Slicing UI/UX

This commit is contained in:
Rizqika 2024-12-04 17:03:58 +07:00
parent 6c115abf48
commit 2a2662df25
34 changed files with 8511 additions and 43 deletions

199
package-lock.json generated
View File

@ -19,6 +19,7 @@
"bootstrap": "^5.3.3", "bootstrap": "^5.3.3",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-bootstrap": "^2.10.6",
"react-copy-to-clipboard": "^5.1.0", "react-copy-to-clipboard": "^5.1.0",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-drag-drop-files": "^2.4.0", "react-drag-drop-files": "^2.4.0",
@ -3201,6 +3202,31 @@
} }
} }
}, },
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@react-aria/ssr": {
"version": "3.9.7",
"resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.7.tgz",
"integrity": "sha512-GQygZaGlmYjmYM+tiNBA5C6acmiDWF52Nqd40bBp0Znk4M4hP+LTmI0lpI1BuKMw45T8RIhrAsICIfKwZvi2Gg==",
"license": "Apache-2.0",
"dependencies": {
"@swc/helpers": "^0.5.0"
},
"engines": {
"node": ">= 12"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@remix-run/router": { "node_modules/@remix-run/router": {
"version": "1.21.0", "version": "1.21.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.21.0.tgz", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.21.0.tgz",
@ -3209,6 +3235,60 @@
"node": ">=14.0.0" "node": ">=14.0.0"
} }
}, },
"node_modules/@restart/hooks": {
"version": "0.4.16",
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz",
"integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==",
"license": "MIT",
"dependencies": {
"dequal": "^2.0.3"
},
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/@restart/ui": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.9.1.tgz",
"integrity": "sha512-qghR21ynHiUrpcIkKCoKYB+3rJtezY5Y7ikrwradCL+7hZHdQ2Ozc5ffxtpmpahoAGgc31gyXaSx2sXXaThmqA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0",
"@popperjs/core": "^2.11.8",
"@react-aria/ssr": "^3.5.0",
"@restart/hooks": "^0.5.0",
"@types/warning": "^3.0.3",
"dequal": "^2.0.3",
"dom-helpers": "^5.2.0",
"uncontrollable": "^8.0.4",
"warning": "^4.0.3"
},
"peerDependencies": {
"react": ">=16.14.0",
"react-dom": ">=16.14.0"
}
},
"node_modules/@restart/ui/node_modules/@restart/hooks": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.5.0.tgz",
"integrity": "sha512-wS+h6IusJCPjTkmOOrRZxIPICD/mtFA3PRZviutoM23/b7akyDGfZF/WS+nIFk27u7JDhPE2+0GBdZxjSqHZkg==",
"license": "MIT",
"dependencies": {
"dequal": "^2.0.3"
},
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/@restart/ui/node_modules/uncontrollable": {
"version": "8.0.4",
"resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz",
"integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==",
"license": "MIT",
"peerDependencies": {
"react": ">=16.14.0"
}
},
"node_modules/@rollup/plugin-babel": { "node_modules/@rollup/plugin-babel": {
"version": "5.3.1", "version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@ -3532,6 +3612,15 @@
"url": "https://github.com/sponsors/gregberge" "url": "https://github.com/sponsors/gregberge"
} }
}, },
"node_modules/@swc/helpers": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
"integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.8.0"
}
},
"node_modules/@testing-library/dom": { "node_modules/@testing-library/dom": {
"version": "8.20.1", "version": "8.20.1",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz",
@ -4152,6 +4241,12 @@
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="
}, },
"node_modules/@types/warning": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz",
"integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==",
"license": "MIT"
},
"node_modules/@types/ws": { "node_modules/@types/ws": {
"version": "8.5.13", "version": "8.5.13",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz",
@ -5419,6 +5514,7 @@
"url": "https://opencollective.com/bootstrap" "url": "https://opencollective.com/bootstrap"
} }
], ],
"license": "MIT",
"peerDependencies": { "peerDependencies": {
"@popperjs/core": "^2.11.8" "@popperjs/core": "^2.11.8"
} }
@ -5700,6 +5796,12 @@
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz",
"integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==" "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA=="
}, },
"node_modules/classnames": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
"license": "MIT"
},
"node_modules/clean-css": { "node_modules/clean-css": {
"version": "5.3.3", "version": "5.3.3",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz",
@ -6650,6 +6752,15 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/destroy": { "node_modules/destroy": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
@ -9212,6 +9323,15 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/ipaddr.js": { "node_modules/ipaddr.js": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz",
@ -13187,6 +13307,25 @@
"react-is": "^16.13.1" "react-is": "^16.13.1"
} }
}, },
"node_modules/prop-types-extra": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz",
"integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==",
"license": "MIT",
"dependencies": {
"react-is": "^16.3.2",
"warning": "^4.0.0"
},
"peerDependencies": {
"react": ">=0.14.0"
}
},
"node_modules/prop-types-extra/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT"
},
"node_modules/prop-types/node_modules/react-is": { "node_modules/prop-types/node_modules/react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@ -13354,6 +13493,36 @@
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
}, },
"node_modules/react-bootstrap": {
"version": "2.10.6",
"resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.6.tgz",
"integrity": "sha512-fNvKytSp0nHts1WRnRBJeBEt+I9/ZdrnhIjWOucEduRNvFRU1IXjZueDdWnBiqsTSJ7MckQJi9i/hxGolaRq+g==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.24.7",
"@restart/hooks": "^0.4.9",
"@restart/ui": "^1.9.0",
"@types/react-transition-group": "^4.4.6",
"classnames": "^2.3.2",
"dom-helpers": "^5.2.1",
"invariant": "^2.2.4",
"prop-types": "^15.8.1",
"prop-types-extra": "^1.1.0",
"react-transition-group": "^4.4.5",
"uncontrollable": "^7.2.1",
"warning": "^4.0.3"
},
"peerDependencies": {
"@types/react": ">=16.14.8",
"react": ">=16.14.0",
"react-dom": ">=16.14.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/react-copy-to-clipboard": { "node_modules/react-copy-to-clipboard": {
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz", "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz",
@ -13509,6 +13678,12 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
}, },
"node_modules/react-lifecycles-compat": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==",
"license": "MIT"
},
"node_modules/react-refresh": { "node_modules/react-refresh": {
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
@ -15808,6 +15983,21 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/uncontrollable": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz",
"integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.6.3",
"@types/react": ">=16.9.11",
"invariant": "^2.2.4",
"react-lifecycles-compat": "^3.0.4"
},
"peerDependencies": {
"react": ">=15.0.0"
}
},
"node_modules/underscore": { "node_modules/underscore": {
"version": "1.12.1", "version": "1.12.1",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz",
@ -16048,6 +16238,15 @@
"makeerror": "1.0.12" "makeerror": "1.0.12"
} }
}, },
"node_modules/warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/watchpack": { "node_modules/watchpack": {
"version": "2.4.2", "version": "2.4.2",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",

View File

@ -14,6 +14,7 @@
"bootstrap": "^5.3.3", "bootstrap": "^5.3.3",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-bootstrap": "^2.10.6",
"react-copy-to-clipboard": "^5.1.0", "react-copy-to-clipboard": "^5.1.0",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-drag-drop-files": "^2.4.0", "react-drag-drop-files": "^2.4.0",

View File

@ -97,6 +97,7 @@ import {
} from './screens/Wa/Verify'; } from './screens/Wa/Verify';
import { import {
Auth,
Auth as AuthWa, Auth as AuthWa,
Message as MessageWa Message as MessageWa
} from './screens/Wa/Verify/Section'; } from './screens/Wa/Verify/Section';
@ -134,6 +135,51 @@ import {
Block as BlockedWa Block as BlockedWa
} from './screens/Wa/Block'; } from './screens/Wa/Block';
import {
Transaction as TransactionElectric,
Verify as VerifyElectric
} from './screens/Identity/Electric';
import {
Transaction as TransactionIdentityNpwp
} from './screens/Identity/Npwp';
import {
Verify as VerifyTax,
Transaction as TransactionTax
} from './screens/Identity/Tax'
import {
Nik as NikTax,
Npwp as NpwpTax
} from './screens/Identity/Tax/Section'
import {
Verify as VerifyIncome,
Transaction as TransactionIncome
} from './screens/Identity/Income';
import {
Verify as VerifyID,
Transaction as TransactionID
} from './screens/Identity/IdVerif';
import {
Verify as VerifyScreening,
Admin as AdminScreening,
Search as SearchScreening,
Transaction as TransactionScreening,
Monitor as MonitorScreening
} from './screens/Watchlist/Screening';
import {
Account,
Company,
Production,
Trial
} from './screens/Profile';
const App = () => { const App = () => {
return ( return (
<Router> <Router>
@ -150,6 +196,12 @@ const App = () => {
<Route path="/application" element={<Applications />} /> <Route path="/application" element={<Applications />} />
<Route path="/createApps" element={<CreateApps />} /> <Route path="/createApps" element={<CreateApps />} />
{/* Profile */}
<Route path="/my-account" element={<Account />} />
<Route path="/company-access" element={<Company />} />
<Route path="/production" element={<Production />} />
<Route path="/trial" element={<Trial />} />
{/* Biometric - Face Recognition (Verify) */} {/* Biometric - Face Recognition (Verify) */}
<Route path="/face-verify/*" element={<FaceVerify />}> <Route path="/face-verify/*" element={<FaceVerify />}>
{/* Anak rute */} {/* Anak rute */}
@ -278,10 +330,39 @@ const App = () => {
<Route path="/wa-inbox" element={<InboxWa />} /> <Route path="/wa-inbox" element={<InboxWa />} />
<Route path="/wa-block" element={<BlockedWa />} /> <Route path="/wa-block" element={<BlockedWa />} />
{/* <Route path="/sms-otp-settings" element={<SmsOtpSettings />} /> */}
{/* Continue for each link */}
{/* Identity - Electric */}
<Route path="/identity-electro-transaction" element={<TransactionElectric />} />
<Route path="/identity-electro-verify" element={<VerifyElectric />} />
{/* Identity - Npwp */}
<Route path="/identity-npwp-transaction" element={<TransactionIdentityNpwp />} />
{/* Identity - Tax */}
<Route path="/identity-tax-verify/*" element={<VerifyTax />}>
{/* Anak rute */}
<Route path="identity-tax-nik" element={<NikTax />} />
<Route path="identity-tax-npwp" element={<NpwpTax />} />
{/* Default route */}
<Route index element={<Navigate to="identity-tax-nik" />} />
</Route>
<Route path="/identity-tax-transaction" element={<TransactionTax />} />
{/* Identity - Income */}
<Route path="/identity-income-verify" element={<VerifyIncome />} />
<Route path="/identity-income-transaction" element={<TransactionIncome />} />
{/* Identity - ID Verifiy */}
<Route path="/identity-id-verify" element={<VerifyID />} />
<Route path="/identity-id-transaction" element={<TransactionID />} />
{/* Watchlist - Screening */}
<Route path="/watchlist-screening-verify" element={<VerifyScreening />} />
<Route path="/watchlist-screening-admin" element={<AdminScreening />} />
<Route path="/watchlist-screening-search" element={<SearchScreening />} />
<Route path="/watchlist-screening-transaction" element={<TransactionScreening />} />
<Route path="/watchlist-screening-monitor" element={<MonitorScreening />} />
</Routes> </Routes>
<Footer /> <Footer />
</div> </div>

View File

@ -1,37 +1,228 @@
// src/components/Profile.js import React, { useState, useEffect } from 'react';
import React from 'react'; import { useNavigate } from 'react-router-dom';
import { ProfileImage } from '../../assets/images'; import { ProfileImage } from '../../assets/images';
import { FaUserCircle, FaBuilding } from 'react-icons/fa';
const Profile = () => ( const Profile = () => {
<div className="profile-section" style={styles.container}> const [show, setShow] = useState(false);
<img const [modalPosition, setModalPosition] = useState({});
src={ProfileImage} const [isMobile, setIsMobile] = useState(false);
alt="Profile" const navigate = useNavigate();
className="profile-image"
style={styles.image} const handleClose = () => setShow(false);
/>
<span className="profile-name" style={styles.name}>Alexander Pierce</span> const handleShow = (event) => {
</div> if (!isMobile) {
); const rect = event.currentTarget.getBoundingClientRect();
setModalPosition({
top: rect.bottom + window.scrollY - 31,
left: rect.left + rect.width / 2 + 274,
});
} else {
setModalPosition({
top: window.scrollY + window.innerHeight / 2 - 150,
left: window.innerWidth / 2 - 2,
});
}
setShow(true);
};
useEffect(() => {
const handleResize = () => {
setIsMobile(window.innerWidth <= 768);
};
handleResize();
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
const handleNavigation = (path) => {
setShow(false);
navigate(path);
};
return (
<>
<div
className="profile-section"
style={styles.container}
onClick={handleShow}
>
<img
src={ProfileImage}
alt="Profile"
className="profile-image"
style={styles.image}
/>
<span className="profile-name" style={styles.name}>
Alexander Pierce
</span>
</div>
{show && (
<div
style={{
...styles.modalContainer,
...modalPosition,
}}
>
<div style={styles.modal}>
<button
style={styles.closeButton}
onClick={handleClose}
>
×
</button>
<div style={styles.modalContent}>
<img
src={ProfileImage}
alt="Profile"
style={styles.modalImage}
/>
<h5>Murtadi Mujaidi</h5>
<p>Mujaidi@gmail.com</p>
<hr />
<div
style={styles.navItem}
onClick={() => handleNavigation('/my-account')}
>
<FaUserCircle style={styles.icon} />
<p style={styles.navText}>My Account</p>
</div>
<div
style={styles.navItem}
onClick={() => handleNavigation('/company-access')}
>
<FaBuilding style={styles.icon} />
<p style={styles.navText}>Company Access</p>
</div>
<div style={styles.accessButtons}>
<button
style={styles.productionButton}
onClick={() => handleNavigation('/production')}
>
PRODUCTION
</button>
<button
style={styles.trialButton}
onClick={() => handleNavigation('/trial')}
>
TRIAL
</button>
</div>
</div>
<button
style={styles.logoutButton}
onClick={handleClose}
>
Log Out
</button>
</div>
</div>
)}
</>
);
};
export default Profile; export default Profile;
const styles = { const styles = {
container: { container: {
padding: '10px', padding: '10px',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
marginTop: '2vh' marginTop: '2vh',
}, cursor: 'pointer',
image: { },
width: '40px', image: {
height: '40px', width: '40px',
borderRadius: '50%', height: '40px',
marginRight: '10px', borderRadius: '50%',
}, marginRight: '10px',
name: { },
fontWeight: 'bold', name: {
}, fontWeight: 'bold',
},
modalContainer: {
position: 'absolute',
zIndex: 1050,
transform: 'translateX(-50%)',
width: '300px',
},
modal: {
backgroundColor: '#fff',
borderRadius: '8px',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
padding: '20px',
textAlign: 'center',
},
closeButton: {
position: 'absolute',
top: '10px',
right: '10px',
background: 'transparent',
border: 'none',
fontSize: '18px',
cursor: 'pointer',
},
modalContent: {
textAlign: 'center',
},
modalImage: {
width: '80px',
height: '80px',
borderRadius: '50%',
marginBottom: '15px',
},
navItem: {
display: 'flex',
alignItems: 'center',
cursor: 'pointer',
borderBottom: '1px solid #ddd',
},
icon: {
marginRight: '10px',
fontSize: '20px',
color: '#4F75FF',
},
navText: {
fontSize: '16px',
color: '#333',
marginTop: '0.8rem'
},
accessButtons: {
display: 'flex',
justifyContent: 'center',
gap: '10px',
marginTop: '20px',
},
productionButton: {
backgroundColor: '#8BE8AB',
color: '#000',
border: 'none',
borderRadius: '4px',
padding: '8px 20px',
fontWeight: 'bold',
cursor: 'pointer',
},
trialButton: {
backgroundColor: '#ddd',
color: '#333',
border: 'none',
borderRadius: '4px',
padding: '8px 20px',
fontWeight: 'bold',
cursor: 'pointer',
},
logoutButton: {
marginTop: '20px',
width: '100%',
backgroundColor: '#0542cc',
color: '#fff',
border: 'none',
borderRadius: '4px',
padding: '10px 0',
fontWeight: 'bold',
cursor: 'pointer',
},
}; };

View File

@ -149,7 +149,7 @@ const dataMenu = [
subMenus: [ subMenus: [
{ name: 'Settings', link: '/wa-settings'}, // Changed the name { name: 'Settings', link: '/wa-settings'}, // Changed the name
{ name: 'Activity Summary', link: '/wa-summary'}, // Changed the name { name: 'Activity Summary', link: '/wa-summary'}, // Changed the name
{ name: 'Transaction Logs', link: '/wa-transaction'}, { name: 'WA Transaction Logs', link: '/wa-transaction'},
{ name: 'Bulk Sending', link: '/wa-bulk'}, // Changed the name { name: 'Bulk Sending', link: '/wa-bulk'}, // Changed the name
], ],
}, },
@ -176,39 +176,39 @@ const dataMenu = [
name: 'Electronic Certificate Verification', // Changed the name name: 'Electronic Certificate Verification', // Changed the name
target: 'collapseElectro', target: 'collapseElectro',
subMenus: [ subMenus: [
{ name: 'Verify Certificate', link: '/identify-electro-verify'}, // Changed the name { name: 'Verify Certificate', link: '/identity-electro-verify'}, // Changed the name
{ name: 'Transaction Logs', link: '/identify-electro-transaction'}, { name: 'Electronic Transaction', link: '/identity-electro-transaction'},
], ],
}, },
{ {
name: 'NPWP Verification', // Changed the name name: 'NPWP Verification', // Changed the name
target: 'collapseIdentifyNpwp', target: 'collapseIdentifyNpwp',
subMenus: [ subMenus: [
{ name: 'Transaction Logs', link: '/identify-npwp-transaction'} { name: 'Npwp Transaction', link: '/identity-npwp-transaction'}
], ],
}, },
{ {
name: 'Tax Number Verification', // Changed the name name: 'Tax Number Verification', // Changed the name
target: 'collapseTax', target: 'collapseTax',
subMenus: [ subMenus: [
{ name: 'Verify Tax Number', link: '/identify-tax-verify'}, // Changed the name { name: 'Verify Tax Number', link: '/identity-tax-verify'}, // Changed the name
{ name: 'Transaction Logs', link: '/identify-tax-transaction'} { name: 'Tax Transaction', link: '/identity-tax-transaction'}
], ],
}, },
{ {
name: 'Income Verification', // Changed the name name: 'Income Verification', // Changed the name
target: 'collapseIncome', target: 'collapseIncome',
subMenus: [ subMenus: [
{ name: 'Verify Income', link: '/identify-income-verify'}, // Changed the name { name: 'Verify Income', link: '/identity-income-verify'}, // Changed the name
{ name: 'Transaction Logs', link: '/identify-income-transaction'} { name: 'Income Transaction', link: '/identity-income-transaction'}
], ],
}, },
{ {
name: 'ID Verification', // Changed the name name: 'ID Verification', // Changed the name
target: 'collapseIdVerification', target: 'collapseIdVerification',
subMenus: [ subMenus: [
{ name: 'Verify ID', link: '/identify-id-verify'}, // Changed the name { name: 'Verify ID', link: '/identity-id-verify'}, // Changed the name
{ name: 'Transaction Logs', link: '/identify-id-transaction'} { name: 'Verify ID Transaction', link: '/identity-id-transaction'}
], ],
}, },
], ],

View File

@ -7,5 +7,5 @@ export {
Navbar, Navbar,
Sidebar, Sidebar,
Main, Main,
Footer Footer,
} }

View File

@ -0,0 +1,411 @@
import React, { useState, useEffect } from 'react';
import { FaChevronLeft, FaChevronRight, FaFastBackward, FaFastForward, FaSort, FaSortUp, FaSortDown } from 'react-icons/fa'; // Icons for sorting
import { NoAvailable } from '../../../assets/icon';
// Pagination Component
const Pagination = ({ currentPage, totalPages, onPageChange }) => {
const handlePrev = () => {
if (currentPage > 1) {
onPageChange(currentPage - 1);
}
};
const handleNext = () => {
if (currentPage < totalPages) {
onPageChange(currentPage + 1);
}
};
const handleFirst = () => {
onPageChange(1); // Go to first page
};
const handleLast = () => {
onPageChange(totalPages); // Go to last page
};
// Logic to display only 3 pages in pagination
const getPaginationRange = () => {
const range = [];
const totalPagesCount = totalPages;
let start = currentPage - 1;
let end = currentPage + 1;
// Adjust start and end if near the boundaries
if (currentPage === 1) {
start = 1;
end = Math.min(3, totalPagesCount);
} else if (currentPage === totalPages) {
start = Math.max(totalPagesCount - 2, 1);
end = totalPagesCount;
}
for (let i = start; i <= end; i++) {
range.push(i);
}
return range;
};
const pageRange = getPaginationRange();
return (
<div className="pagination-container d-flex justify-content-end mt-4">
{/* First Page Button */}
<button
className="btn"
onClick={handleFirst}
disabled={currentPage === 1}
>
<FaFastBackward /> {/* Double Arrow Left */}
</button>
<button
className="btn"
onClick={handlePrev}
disabled={currentPage === 1}
>
<FaChevronLeft /> {/* Single Arrow Left */}
</button>
{/* Page Numbers */}
{pageRange.map((pageNum) => (
<button
key={pageNum}
className={`btn ${pageNum === currentPage ? 'btn-primary' : ''}`}
onClick={() => onPageChange(pageNum)}
>
{pageNum}
</button>
))}
<button
className="btn"
onClick={handleNext}
disabled={currentPage === totalPages}
>
<FaChevronRight /> {/* Single Arrow Right */}
</button>
{/* Last Page Button */}
<button
className="btn"
onClick={handleLast}
disabled={currentPage === totalPages}
>
<FaFastForward /> {/* Double Arrow Right */}
</button>
</div>
);
};
const Transaction = () => {
const [currentPage, setCurrentPage] = useState(1);
const [isMobile, setIsMobile] = useState(false); // State to detect mobile view
const [transactionData, setTransactionData] = useState([]);
const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc' }); // Sorting state
const dataPerPage = 10; // Data per page (10 data per page)
const buttonData = [
{ label: 'Copy', enabled: true },
{ label: 'CSV', enabled: true },
{ label: 'Excel', enabled: true },
{ label: 'PDF', enabled: true },
{ label: 'Print', enabled: true },
{ label: 'Column Visibility', enabled: true },
];
// Generate 691 dummy transactions
const generateDummyData = (numOfItems) => {
const transactionData = [];
for (let i = 1; i <= numOfItems; i++) {
transactionData.push({
transactionId: `TX${String(i).padStart(3, '0')}`, // Transaction ID
applicationName: `App ${Math.floor(Math.random() * 5) + 1}`, // Application Name
livenessId: `LIV${String(i).padStart(3, '0')}`, // Liveness ID (baru ditambahkan)
serviceCharged: (Math.random() * 100).toFixed(2), // Service Charged (angka acak dengan 2 desimal)
referenceId: `REF${String(i).padStart(3, '0')}`, // Reference ID
status: ['Completed', 'Pending', 'Failed'][Math.floor(Math.random() * 3)], // Status acak
mode: Math.random() > 0.5 ? 'Online' : 'Offline', // Mode acak (Online atau Offline)
});
}
return transactionData;
};
// Set the generated transaction data
useEffect(() => {
setTransactionData(generateDummyData(122)); // count data dummy transactions
}, []);
// Sorting function
const sortData = (data, config) => {
const { key, direction } = config;
return [...data].sort((a, b) => {
if (a[key] < b[key]) {
return direction === 'asc' ? -1 : 1;
}
if (a[key] > b[key]) {
return direction === 'asc' ? 1 : -1;
}
return 0;
});
};
// Handle column header sort click
const handleSort = (key) => {
let direction = 'asc';
if (sortConfig.key === key && sortConfig.direction === 'asc') {
direction = 'desc'; // Toggle direction if the same column is clicked
}
setSortConfig({ key, direction });
};
// Get the paginated data
const getPaginatedData = (data, page, perPage) => {
const sortedData = sortData(data, sortConfig);
const startIndex = (page - 1) * perPage;
const endIndex = startIndex + perPage;
return sortedData.slice(startIndex, endIndex);
};
// Handle page change
const handlePageChange = (page) => {
setCurrentPage(page);
};
// Calculate total pages based on the data and data per page
const totalPages = Math.ceil(transactionData.length / dataPerPage);
// Paginated data
const paginatedData = getPaginatedData(transactionData, currentPage, dataPerPage);
// Detect screen size and update isMobile state
useEffect(() => {
const handleResize = () => {
setIsMobile(window.innerWidth <= 768); // Change 768 to your breakpoint
};
handleResize();
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return (
<div className="container mt-5">
{/* Welcome Message */}
<div className="row-card border-left border-primary shadow mb-4" style={{ backgroundColor: '#E2FBEA' }}>
<div className="d-flex flex-column justify-content-start align-items-start p-4">
<div>
<h4 className="mb-3 text-start">
<i className="fas fa-warning fa-bold me-3"></i>Alert
</h4>
<p className="mb-0 text-start">
Get started now by creating an Application ID and explore all the demo services available on the dashboard.
Experience the ease and flexibility of trying out all our features firsthand.
</p>
</div>
</div>
</div>
<div style={styles.contentContainer}>
{/* Filter Form */}
<div className="card p-3 mb-4">
<div className="row">
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<label>Start Date</label>
<input type="date" className="form-control" />
</div>
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<label>End Date</label>
<input type="date" className="form-control" />
</div>
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<label>Application</label>
<select className="form-control">
<option>Select Application</option>
<option>App 1</option>
<option>App 2</option>
<option>App 3</option>
<option>App 4</option>
<option>App 5</option>
</select>
</div>
<div className={`col-12 ${isMobile ? 'd-flex justify-content-between' : 'col-md-2 d-flex align-items-end'}`} style={{ gap: '10px' }}>
<button className="btn btn-primary w-48">Apply</button>
<button className="btn btn-secondary w-48">Cancel</button>
</div>
</div>
</div>
{/* Action Buttons */}
<div className="d-flex justify-content-between align-items-center mb-3">
<div>
{buttonData.map((button, index) =>
button.enabled ? (
<button
key={index}
className={`btn btn-light ${isMobile ? 'mb-2' : ''}`} // Add margin on mobile
style={styles.actionButton}
>
{button.label}
</button>
) : null
)}
</div>
{/* Search Bar with Icon */}
<div className="input-group" style={{ width: '250px', display: 'flex', alignItems: 'center', justifyContent: 'flex-end' }}>
<input
type="text"
placeholder="Search..."
className="form-control"
/>
<span className="input-group-text">
<i className="fas fa-search"></i> {/* FontAwesome search icon */}
</span>
</div>
</div>
{/* Table */}
<div className="table-responsive">
<table className="table table-bordered" style={styles.tableContainer}>
<thead>
<tr>
<th>No.</th> {/* Kolom untuk Nomor Urut */}
<th>
<button className="btn" onClick={() => handleSort('transactionId')}>
Transaction ID
{sortConfig.key === 'transactionId' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'transactionId' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('applicationName')}>
Application Name
{sortConfig.key === 'applicationName' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'applicationName' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('livenessId')}>
Liveness ID
{sortConfig.key === 'livenessId' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'livenessId' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('serviceCharged')}>
Service Charged
{sortConfig.key === 'serviceCharged' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'serviceCharged' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('referenceId')}>
Reference ID
{sortConfig.key === 'referenceId' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'referenceId' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('status')}>
Status
{sortConfig.key === 'status' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'status' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('mode')}>
Mode
{sortConfig.key === 'mode' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'mode' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
</tr>
</thead>
<tbody>
{paginatedData.length > 0 ? (
paginatedData.map((transaction, index) => (
<tr key={index}>
{/* Kolom Nomor Urut */}
<td>{(currentPage - 1) * dataPerPage + index + 1}</td> {/* Nomor urut berdasarkan halaman dan index */}
<td>{transaction.transactionId}</td>
<td>{transaction.applicationName}</td>
<td>{transaction.livenessId}</td>
<td>{transaction.serviceCharged}</td>
<td>{transaction.referenceId}</td>
<td>{transaction.status}</td>
<td>{transaction.mode}</td>
</tr>
))
) : (
<tr>
<td colSpan="7" className="text-center">
<div className="d-flex flex-column align-items-center mt-5">
<img src={NoAvailable} alt="No Data Available" className="mb-3" style={styles.iconStyle} />
<p>Data not available</p>
</div>
</td>
</tr>
)}
</tbody>
</table>
</div>
{/* Pagination */}
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={handlePageChange}
/>
</div>
</div>
);
};
export default Transaction;
const styles = {
contentContainer: {
padding: '20px',
border: '0.1px solid rgba(0, 0, 0, 0.2)',
borderLeft: '4px solid #0542cc',
borderRadius: '10px',
width: '100%',
},
tableContainer: {
minHeight: '300px',
maxHeight: '1500px',
overflowY: 'auto',
},
iconStyle: {
width: '50px',
height: '50px',
},
// Add margin-left style for icons
iconMarginLeft: {
marginLeft: '0.7rem', // Adjust as needed
},
};

View File

@ -0,0 +1,843 @@
import React, { useState, useRef, useEffect } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faChevronLeft, faChevronDown, faTimes, faImage } from '@fortawesome/free-solid-svg-icons';
import { FileUploader } from 'react-drag-drop-files';
import Select from 'react-select'
import { ServerDownAnimation } from '../../../assets/images';
import { Link, Routes, Route, useNavigate } from 'react-router-dom';
const Verify = () => {
const BASE_URL = process.env.REACT_APP_BASE_URL
const API_KEY = process.env.REACT_APP_API_KEY
const [email, setEmail] = useState('');
const [emailError, setEmailError] = useState('');
const [nik, setNik] = useState('');
const [nikError, setNikError] = useState('');
const [date, setDate] = useState('');
const [dateError, setDateError] = useState('');
const [phoneId, setPhoneId] = useState('');
const [phoneError, setPhoneError] = useState('');
const [fullName, setFullName] = useState('');
const [fullNameError, setFullNameError] = useState('');
const [errorMessage, setErrorMessage] = useState('');
const [SelfieImage, setSelfieImage] = useState('');
const [KtpImage, setKtpImage] = useState('');
const [resultImageLabel, setResultImageLabel] = useState('');
const [resultCompareImageLabel, setResultCompareImageLabel] = useState('');
const fileInputRef = useRef(null);
const fileCompareInputRef = useRef(null);
const [showResult, setShowResult] = useState(false);
const [applicationId, setApplicationId] = useState('');
const [selectedQuota, setSelectedQuota] = useState(0);
const [isLoading, setIsLoading] = useState(false);
const [imageUrl, setImageUrl] = useState('');
const [imageCompareUrl, setImageCompareUrl] = useState('');
const [verified, setVerified] = useState(null);
const fileTypes = ["JPG", "JPEG", "PNG"];
const [file, setFile] = useState(null); // For the first image
const [compareFile, setCompareFile] = useState(null); // For the second imag
const [applicationIds, setApplicationIds] = useState([]);
const [inputValueApplication, setInputValueApplication] = useState(''); // Controlled input value for Application ID
const [isServer, setIsServer] = useState(true);
const [applicationError, setApplicationError] = useState('');
const [SelfieImageError, setSelfieImageError] = useState('');
const [KtpImageError, setKtpImageError] = useState('');
useEffect(() => {
const fetchApplicationIds = async () => {
try {
setIsLoading(true)
const url = `${BASE_URL}/application/list`;
console.log('Fetching URL:', url); // Log the URL
const response = await fetch(url, {
method: 'GET',
headers: {
'accept': 'application/json',
'x-api-key': `${API_KEY}`,
},
});
const data = await response.json();
if (data.status_code === 200) {
const ids = data.details.data.map(app => app.id);
setIsServer(true)
console.log('Application Id: ' + ids); // Log the IDs
setApplicationIds(data.details.data); // Update state with the fetched data
} else {
console.error('Failed to fetch data:', data.details.message);
setIsServer(false)
}
} catch (error) {
console.error('Error fetching application IDs:', error);
setIsServer(false)
} finally {
setIsLoading(false)
}
};
fetchApplicationIds();
}, []);
const handleApplicationChange = (selectedOption) => {
if (selectedOption) {
const selectedId = selectedOption.value;
const selectedApp = applicationIds.find(app => app.id === parseInt(selectedId));
if (selectedApp) {
setApplicationId(selectedId);
setSelectedQuota(selectedApp.quota); // Set the selected quota
}
}
};
const handleInputChangeApplication = (newInputValue) => {
// Limit input to 15 characters for Application ID
if (newInputValue.length <= 15) {
setInputValueApplication(newInputValue);
}
};
const handleImageUpload = (file) => {
if (file && fileTypes.includes(file.name.split('.').pop().toUpperCase())) {
setSelfieImage(file.name);
setFile(file); // Store the file directly in state
setSelfieImageError(''); // Clear error if valid
}
};
const handleCompareImageUpload = (file) => {
if (file && fileTypes.includes(file.name.split('.').pop().toUpperCase())) {
setKtpImage(file.name);
setCompareFile(file); // Store the compare file directly in state
setKtpImageError(''); // Clear error if valid
}
};
const handleImageCancel = () => {
setSelfieImage('');
setImageUrl('');
if (fileInputRef.current) fileInputRef.current.value = '';
};
const handleCompareImageCancel = () => {
setKtpImage('');
setImageCompareUrl('');
if (fileCompareInputRef.current) fileCompareInputRef.current.value = '';
};
const handleCheckClick = async () => {
// Reset error messages
setApplicationError('');
setSelfieImageError('');
setKtpImageError('');
setErrorMessage('');
setEmailError('')
setDateError('')
setNikError('')
// Initialize a flag to check for errors
let hasError = false;
// Validate Application ID
if (!applicationId) {
setApplicationError('Please select an Application ID before compare.');
hasError = true;
}
// Validate Image Uploads
if (!SelfieImage) {
setSelfieImageError('Please upload a face photo before compare.');
hasError = true;
}
if (!KtpImage) {
setKtpImageError('Please upload a compare face photo before compare.');
hasError = true;
}
// If there are any errors, return early
if (hasError) {
return;
}
// Prepare FormData and log inputs
const formData = new FormData();
formData.append('application_id', applicationId);
formData.append('file1', file);
formData.append('file2', compareFile);
// Log the inputs
console.log('Inputs:', {
applicationId,
file1: SelfieImage,
file2: KtpImage,
});
setIsLoading(true);
setErrorMessage('');
try {
const response = await fetch(`${BASE_URL}/face_recognition/compare`, {
method: 'POST',
headers: {
'accept': 'application/json',
'x-api-key': `${API_KEY}`,
},
body: formData,
});
const data = await response.json();
if (response.ok) {
// Fetch image URLs from response
const imageUrl1 = data.details.data.result.image_url1;
const imageUrl2 = data.details.data.result.image_url2;
await fetchImage(imageUrl1, setImageUrl);
await fetchImage(imageUrl2, setImageCompareUrl);
setVerified(data.details.data.result.verified);
setResultImageLabel(SelfieImage)
setResultCompareImageLabel(KtpImage)
setSelectedQuota(data.details.data.result.quota)
setShowResult(true);
console.log('Comparison successful:', data);
} else {
console.error('Error response:', data);
const errorMessage = data.message || data.detail || data.details?.message || 'An unknown error occurred.';
setErrorMessage(errorMessage);
}
} catch (error) {
console.error('Error:', error);
setErrorMessage('An error occurred while making the request.');
} finally {
setIsLoading(false);
}
};
const handleEmailChange = (e) => {
setEmail(e.target.value);
};
const handleDateChange = (e) => {
setDate(e.target.value);
};
const handleNikChange = (e) => {
setNik(e.target.value);
};
const handleFullNameChange = (e) => {
setFullName(e.target.value);
};
const fetchImage = async (imageUrl, setImageUrl) => {
setIsLoading(true);
try {
const response = await fetch(imageUrl, {
method: 'GET',
headers: {
'accept': 'application/json',
'x-api-key': `${API_KEY}`, // Ensure this is valid
}
});
if (!response.ok) {
const errorDetails = await response.json();
console.error('Image fetch error details:', errorDetails);
setErrorMessage('Failed to fetch image, please try again.');
return;
}
const imageBlob = await response.blob();
const imageData = URL.createObjectURL(imageBlob);
console.log('Fetched image URL:', imageData);
setImageUrl(imageData); // Set the state with the blob URL
} catch (error) {
console.error('Error fetching image:', error);
setErrorMessage(error.message);
} finally {
setIsLoading(false);
}
};
const applicationOptions = applicationIds.map(app => ({
value: app.id,
label: app.name
}));
const ResultsSection = ({ showResult, verified, imageUrl, SelfieImage, imageCompareUrl, KtpImage }) => (
showResult && (
<div style={styles.containerResultStyle}>
<h1 style={{ color: '#0542cc', fontSize: '1.5rem', textAlign: 'center' }}>Results</h1>
<div style={styles.resultContainer}>
<table style={styles.tableStyle}>
<tbody>
<tr>
<td style={{ border: '0.1px solid gray', padding: '8px', width: '30%' }}>Similarity</td>
<td style={styles.similarityText(verified)}>
{verified !== null ? (verified ? 'True' : 'False') : 'N/A'}
</td>
</tr>
</tbody>
</table>
<div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'center', gap: '20px' }}>
<div style={styles.imageContainer}>
<img
src={imageUrl || "path-to-your-image"}
alt="Original Foto"
style={styles.imageStyle}
/>
<p style={{ marginTop: '1rem', textAlign: 'center' }}>File Name: {resultImageLabel}</p>
</div>
<div style={styles.imageCompareContainer}>
<img
src={imageCompareUrl || "path-to-your-image"}
alt="Compare Foto"
style={styles.imageStyle}
/>
<p style={{ marginTop: '1rem', textAlign: 'center' }}>File Name: {resultCompareImageLabel}</p>
</div>
</div>
</div>
</div>
)
);
const formatFileSize = (sizeInBytes) => {
if (sizeInBytes < 1024) {
return `${sizeInBytes} bytes`; // Jika ukuran lebih kecil dari 1 KB
} else if (sizeInBytes < 1048576) {
return `${(sizeInBytes / 1024).toFixed(2)} KB`; // Jika ukuran lebih kecil dari 1 MB
} else {
return `${(sizeInBytes / 1048576).toFixed(2)} MB`; // Jika ukuran lebih besar dari 1 MB
}
};
if (!isServer) {
return (
<div style={{ textAlign: 'center', marginTop: '50px' }}>
<img
src={ServerDownAnimation}
alt="Server Down Animation"
style={{ width: '18rem', height: '18rem', marginBottom: '20px' }}
/>
<h2 style={{ color: 'red' }}>Server tidak dapat diakses</h2>
<p>{errorMessage || 'Silakan periksa koneksi internet Anda atau coba lagi nanti.'}</p>
<button
onClick={() => window.location.reload()}
style={{
padding: '10px 20px',
backgroundColor: '#0542cc',
color: '#fff',
border: 'none',
borderRadius: '5px',
cursor: 'pointer'
}}>
Coba Lagi
</button>
</div>
);
}
return (
<div className="container" style={styles.container}>
{/* Inject keyframes for the spinner */}
<style>
{`
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
},
@media (max-width: 768px) {
.resultContainer {
flex-direction: column;
align-items: center;
}
}
`}
</style>
{isLoading && (
<div style={styles.loadingOverlay}>
<div style={styles.spinner}></div>
<p style={styles.loadingText}>Loading...</p>
</div>
)}
{/* Static Content */}
<div className="row-card border-left border-primary shadow mb-4" style={styles.welcomeCard}>
<div className="d-flex flex-column justify-content-start align-items-start p-4">
<h4 className="mb-3 text-start">
<i className="fas fa-warning fa-bold me-3"></i>Alert
</h4>
<p className="mb-0 text-start">
Get started now by creating an Application ID and explore all the demo services available on the dashboard.
Experience the ease and flexibility of trying out all our features firsthand.
</p>
<div className="d-flex flex-row mt-3">
<Link to="/createApps" style={{ textDecoration: 'none' }}>
<button className="btn d-flex justify-content-center align-items-center me-2" style={styles.createButton}>
<i className="fas fa-plus text-white me-2"></i>
<p className="text-white mb-0">Create New App ID</p>
</button>
</Link>
</div>
</div>
</div>
<div style={styles.section}>
{/* Application ID Selection */}
<div className="form-group row align-items-center">
<div className="col-md-6">
<div className="select-wrapper">
<Select
id="applicationId"
value={applicationOptions.find(option => option.value === applicationId)}
onChange={handleApplicationChange} // Pass selected option directly
options={applicationOptions}
placeholder="Select Application ID"
isSearchable
menuPortalTarget={document.body}
menuPlacement="auto"
inputValue={inputValueApplication}
onInputChange={handleInputChangeApplication}
/>
</div>
{applicationError && <small style={styles.SelfieImageError}>{applicationError}</small>}
</div>
<div className="col-md-6">
<p className="text-secondary" style={{ fontSize: '16px', fontWeight: '400', margin: '0', marginTop: '8px' }}>
Remaining Quota
</p>
<div style={styles.remainingQuota}>
<span style={styles.quotaText}>{selectedQuota}</span> {/* Display selected quota */}
<span style={styles.timesText}>(times)</span>
</div>
</div>
</div>
{/* Nik and Date Row */}
<div className="form-group row align-items-center">
{/* Kolom untuk input email */}
<div className="col-md-6">
<div className="input-wrapper">
<input
type="nik"
id="nik"
className="form-control"
value={nik}
onChange={handleNikChange}
placeholder="NIK"
/>
</div>
{/* Menampilkan error jika ada */}
{nikError && <small style={styles.SelfieImageError}>{nikError}</small>}
</div>
{/* Kolom untuk input tanggal */}
<div className="col-md-6">
<div className="input-wrapper">
<input
type="date"
id="date"
className="form-control"
value={date}
onChange={handleDateChange}
/>
</div>
{/* Menampilkan error jika ada */}
{dateError && <small style={styles.SelfieImageError}>{dateError}</small>}
</div>
</div>
{/* Email and Phone */}
<div className="form-group row align-items-center my-4">
{/* Kolom untuk input email */}
<div className="col-md-6">
<div className="input-wrapper">
<input
type="email"
id="email"
className="form-control"
value={email}
onChange={handleEmailChange}
placeholder="Enter Email"
/>
</div>
{/* Menampilkan error jika ada */}
{emailError && <small style={styles.SelfieImageError}>{emailError}</small>}
</div>
<div className="col-md-6">
<div className="input-group">
<span className="input-group-prepend">
<span className="input-group-text">+62</span>
</span>
<input
type="text"
id="phoneId"
className="form-control"
placeholder="Phone Number"
value={phoneId}
onChange={(e) => setPhoneId(e.target.value)}
/>
</div>
{/* Menampilkan error jika ada */}
{phoneError && <small style={styles.SelfieImageError}>{phoneError}</small>}
</div>
</div>
{/* FullName */}
<div className="form-group row align-items-center">
{/* Kolom untuk input full name */}
<div className="col-md-12">
<div className="input-wrapper">
<input
type="text" // Ubah tipe input menjadi text untuk full name
id="fullName" // Ganti id menjadi "fullName"
className="form-control"
value={fullName} // Gunakan state fullName
onChange={handleFullNameChange} // Fungsi yang menangani perubahan input
placeholder="Full Name" // Ubah placeholder menjadi Full Name
/>
</div>
{/* Menampilkan error jika ada */}
{fullNameError && <small style={styles.SelfieImageError}>{fullNameError}</small>}
</div>
</div>
{/* Image Row */}
<div className="row">
{/* Upload Image #1 */}
<div className="col-md-6">
<div className="row form-group mt-4">
<label style={{ fontWeight: 600, fontSize: '14px', color: '#212529' }}>Selfie Photo</label>
<FileUploader
handleChange={handleImageUpload}
name="file"
types={fileTypes}
multiple={false}
children={
<div style={styles.uploadArea}>
<i className="fas fa-cloud-upload-alt" style={styles.uploadIcon}></i>
<p style={styles.uploadText}>Drag and Drop Here</p>
<p>Or</p>
<a href="#">Browse</a>
<p className="text-muted">Recommended size: 250x250 (Max File Size: 2MB)</p>
<p className="text-muted">Supported file types: JPG, JPEG</p>
</div>
}
/>
{SelfieImageError && <small style={styles.SelfieImageError}>{SelfieImageError}</small>}
</div>
{/* Display uploaded image name */}
{SelfieImage && (
<div className="mt-3">
<p><strong>File:</strong> {SelfieImage}</p>
{file && (
<p style={styles.fileSize}>
Size: {formatFileSize(file.size)}
</p>
)}
<button className="btn btn-danger" onClick={handleImageCancel}>
<FontAwesomeIcon icon={faTimes} className="me-2" />Cancel
</button>
</div>
)}
</div>
{errorMessage && <small style={styles.SelfieImageError}>{errorMessage}</small>}
{/* Upload Image #2 */}
<div className="col-md-6">
<div className="row form-group mt-4">
<label style={{ fontWeight: 600, fontSize: '14px', color: '#212529' }}>KTP Photo</label>
<FileUploader
handleChange={handleCompareImageUpload}
name="file"
types={fileTypes}
multiple={false}
children={
<div style={styles.uploadArea}>
<i className="fas fa-cloud-upload-alt" style={styles.uploadIcon}></i>
<p style={styles.uploadText}>Drag and Drop Here</p>
<p>Or</p>
<a href="#">Browse</a>
<p className="text-muted">Recommended size: 250x250 (Max File Size: 2MB)</p>
<p className="text-muted">Supported file types: JPG, JPEG</p>
</div>
}
/>
{KtpImageError && <small style={styles.SelfieImageError}>{KtpImageError}</small>}
</div>
{/* Display uploaded image name */}
{KtpImage && (
<div className="mt-3">
<p><strong>File:</strong> {KtpImage}</p>
{compareFile && (
<p style={styles.fileSize}>
Size: {formatFileSize(compareFile.size)}
</p>
)}
<button className="btn btn-danger" onClick={handleCompareImageCancel}>
<FontAwesomeIcon icon={faTimes} className="me-2" />Cancel
</button>
</div>
)}
</div>
</div>
{/* Submit Button */}
<div style={styles.submitButton}>
<button onClick={handleCheckClick} className="btn d-flex justify-content-center align-items-center me-2" style={{ backgroundColor: '#0542CC' }}>
<p className="text-white mb-0">Check Now</p>
</button>
</div>
{/* Results Section */}
{showResult && (
<ResultsSection
showResult={showResult}
verified={verified}
imageUrl={imageUrl}
SelfieImage={SelfieImage}
imageCompareUrl={imageCompareUrl}
KtpImage={KtpImage}
/>
)}
</div>
</div>
)
}
export default Verify
const styles = {
container: {
marginTop: '3%',
padding: '0 15px',
},
welcomeCard: {
backgroundColor: '#E2FBEA',
borderLeft: '4px solid #0542CC',
borderRadius: '5px',
marginBottom: '20px',
},
createButton: {
backgroundColor: '#0542CC',
},
section: {
padding: '20px',
border: '0.1px solid rgba(0, 0, 0, 0.2)',
borderLeft: '4px solid #0542CC',
borderRadius: '10px',
width: '100%',
},
formGroup: {
marginTop: '-45px',
},
selectWrapper: {
position: 'relative',
marginTop: '0',
},
select: {
width: '100%',
paddingRight: '30px',
},
chevronIcon: {
position: 'absolute',
right: '10px',
top: '50%',
transform: 'translateY(-50%)',
pointerEvents: 'none',
},
remainingQuota: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
marginTop: '4px',
},
quotaText: {
fontSize: '40px',
color: '#0542cc',
fontWeight: '600',
},
timesText: {
marginLeft: '8px',
verticalAlign: 'super',
fontSize: '20px',
},
uploadArea: {
backgroundColor: '#e6f2ff',
height: '40svh',
cursor: 'pointer',
marginTop: '1rem',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
border: '1px solid #ced4da',
borderRadius: '0.25rem',
padding: '25px 10px 10px 10px'
},
uploadIcon: {
fontSize: '40px',
color: '#0542cc',
marginBottom: '7px',
},
uploadText: {
color: '#1f2d3d',
fontWeight: '400',
fontSize: '16px',
lineHeight: '13px',
},
wrapper: {
border: '1px solid #ddd',
borderRadius: '6px',
padding: '18px 10px 0 8px', // Padding lebih seragam
height: '13svh', // Tinggi lebih kecil untuk menyesuaikan tampilan
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
backgroundColor: '#f9f9f9',
overflow: 'hidden',
},
fileWrapper: {
display: 'flex',
alignItems: 'center',
flex: '1',
},
textContainer: {
flex: '1',
fontSize: '16px', // Ukuran font lebih kecil
marginLeft: '6px',
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
marginTop: '1rem'
},
fileSize: {
fontSize: '12px',
color: '#555',
marginBottom: '2rem',
},
closeButtonContainer: {
display: 'flex',
alignItems: 'center',
marginLeft: 'auto',
},
closeButton: {
background: 'transparent',
border: 'none',
cursor: 'pointer',
padding: '0',
},
imageIcon: {
color: '#0542cc',
fontSize: '18px', // Ukuran ikon sedikit lebih kecil
marginRight: '6px',
},
closeIcon: {
color: 'red',
fontSize: '18px',
},
submitButton: {
marginLeft: 'auto',
marginTop: '4rem',
textAlign: 'start',
position: 'relative',
zIndex: 1,
},
SelfieImageError: {
color: 'red',
fontSize: '12px',
marginTop: '5px',
},
containerResultStyle: {
margin: '20px 0',
padding: '10px',
border: '1px solid #e0e0e0',
borderRadius: '8px',
backgroundColor: '#f9f9f9',
},
resultContainer: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
tableStyle: {
width: '100%',
borderCollapse: 'collapse',
marginBottom: '20px',
},
imageContainer: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
flex: 1,
padding: '10px',
},
imageCompareContainer: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
flex: 1,
padding: '10px',
},
imageStyle: {
width: '100%',
height: 'auto',
maxWidth: '150px', // Limit image width
borderRadius: '8px',
},
similarityText: (verified) => ({
border: '0.1px solid gray',
padding: '8px',
color: verified ? 'green' : 'red',
fontWeight: 'bold',
}),
loadingOverlay: {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.2)',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
zIndex: 1000,
},
spinner: {
border: '4px solid rgba(0, 0, 0, 0.1)',
borderLeftColor: '#0542cc',
borderRadius: '50%',
width: '90px',
height: '90px',
animation: 'spin 1s ease-in-out infinite',
},
loadingText: {
marginTop: '10px',
fontSize: '1.2rem',
color: '#fff',
textAlign: 'center',
},
};

View File

@ -0,0 +1,7 @@
import Transaction from './Transaction';
import Verify from './Verify';
export {
Transaction,
Verify
}

View File

@ -0,0 +1,411 @@
import React, { useState, useEffect } from 'react';
import { FaChevronLeft, FaChevronRight, FaFastBackward, FaFastForward, FaSort, FaSortUp, FaSortDown } from 'react-icons/fa'; // Icons for sorting
import { NoAvailable } from '../../../assets/icon';
// Pagination Component
const Pagination = ({ currentPage, totalPages, onPageChange }) => {
const handlePrev = () => {
if (currentPage > 1) {
onPageChange(currentPage - 1);
}
};
const handleNext = () => {
if (currentPage < totalPages) {
onPageChange(currentPage + 1);
}
};
const handleFirst = () => {
onPageChange(1); // Go to first page
};
const handleLast = () => {
onPageChange(totalPages); // Go to last page
};
// Logic to display only 3 pages in pagination
const getPaginationRange = () => {
const range = [];
const totalPagesCount = totalPages;
let start = currentPage - 1;
let end = currentPage + 1;
// Adjust start and end if near the boundaries
if (currentPage === 1) {
start = 1;
end = Math.min(3, totalPagesCount);
} else if (currentPage === totalPages) {
start = Math.max(totalPagesCount - 2, 1);
end = totalPagesCount;
}
for (let i = start; i <= end; i++) {
range.push(i);
}
return range;
};
const pageRange = getPaginationRange();
return (
<div className="pagination-container d-flex justify-content-end mt-4">
{/* First Page Button */}
<button
className="btn"
onClick={handleFirst}
disabled={currentPage === 1}
>
<FaFastBackward /> {/* Double Arrow Left */}
</button>
<button
className="btn"
onClick={handlePrev}
disabled={currentPage === 1}
>
<FaChevronLeft /> {/* Single Arrow Left */}
</button>
{/* Page Numbers */}
{pageRange.map((pageNum) => (
<button
key={pageNum}
className={`btn ${pageNum === currentPage ? 'btn-primary' : ''}`}
onClick={() => onPageChange(pageNum)}
>
{pageNum}
</button>
))}
<button
className="btn"
onClick={handleNext}
disabled={currentPage === totalPages}
>
<FaChevronRight /> {/* Single Arrow Right */}
</button>
{/* Last Page Button */}
<button
className="btn"
onClick={handleLast}
disabled={currentPage === totalPages}
>
<FaFastForward /> {/* Double Arrow Right */}
</button>
</div>
);
};
const Transaction = () => {
const [currentPage, setCurrentPage] = useState(1);
const [isMobile, setIsMobile] = useState(false); // State to detect mobile view
const [transactionData, setTransactionData] = useState([]);
const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc' }); // Sorting state
const dataPerPage = 10; // Data per page (10 data per page)
const buttonData = [
{ label: 'Copy', enabled: true },
{ label: 'CSV', enabled: true },
{ label: 'Excel', enabled: true },
{ label: 'PDF', enabled: true },
{ label: 'Print', enabled: true },
{ label: 'Column Visibility', enabled: true },
];
// Generate 691 dummy transactions
const generateDummyData = (numOfItems) => {
const transactionData = [];
for (let i = 1; i <= numOfItems; i++) {
transactionData.push({
transactionId: `TX${String(i).padStart(3, '0')}`, // Transaction ID
applicationName: `App ${Math.floor(Math.random() * 5) + 1}`, // Application Name
referenceId: `REF${String(i).padStart(3, '0')}`, // Reference ID
dateSent: new Date(2023, Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1).toLocaleDateString(), // Date Sent (random date)
mode: Math.random() > 0.5 ? 'Online' : 'Offline', // Method (randomly Online or Offline)
status: ['Completed', 'Pending', 'Failed'][Math.floor(Math.random() * 3)], // Status acak
});
}
return transactionData;
};
// Set the generated transaction data
useEffect(() => {
setTransactionData(generateDummyData(122)); // count data dummy transactions
}, []);
// Sorting function
const sortData = (data, config) => {
const { key, direction } = config;
return [...data].sort((a, b) => {
if (a[key] < b[key]) {
return direction === 'asc' ? -1 : 1;
}
if (a[key] > b[key]) {
return direction === 'asc' ? 1 : -1;
}
return 0;
});
};
// Handle column header sort click
const handleSort = (key) => {
let direction = 'asc';
if (sortConfig.key === key && sortConfig.direction === 'asc') {
direction = 'desc'; // Toggle direction if the same column is clicked
}
setSortConfig({ key, direction });
};
// Get the paginated data
const getPaginatedData = (data, page, perPage) => {
const sortedData = sortData(data, sortConfig);
const startIndex = (page - 1) * perPage;
const endIndex = startIndex + perPage;
return sortedData.slice(startIndex, endIndex);
};
// Handle page change
const handlePageChange = (page) => {
setCurrentPage(page);
};
// Calculate total pages based on the data and data per page
const totalPages = Math.ceil(transactionData.length / dataPerPage);
// Paginated data
const paginatedData = getPaginatedData(transactionData, currentPage, dataPerPage);
// Detect screen size and update isMobile state
useEffect(() => {
const handleResize = () => {
setIsMobile(window.innerWidth <= 768); // Change 768 to your breakpoint
};
handleResize();
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return (
<div className="container mt-5">
{/* Welcome Message */}
<div className="row-card border-left border-primary shadow mb-4" style={{ backgroundColor: '#E2FBEA' }}>
<div className="d-flex flex-column justify-content-start align-items-start p-4">
<div>
<h4 className="mb-3 text-start">
<i className="fas fa-warning fa-bold me-3"></i>Alert
</h4>
<p className="mb-0 text-start">
Get started now by creating an Application ID and explore all the demo services available on the dashboard.
Experience the ease and flexibility of trying out all our features firsthand.
</p>
</div>
</div>
</div>
<div style={styles.contentContainer}>
{/* Filter Form */}
<div className="card p-3 mb-4">
<div className="row">
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<label>Start Date</label>
<input type="date" className="form-control" />
</div>
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<label>End Date</label>
<input type="date" className="form-control" />
</div>
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<label>Application</label>
<select className="form-control">
<option>Select Application</option>
<option>App 1</option>
<option>App 2</option>
<option>App 3</option>
<option>App 4</option>
<option>App 5</option>
</select>
</div>
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<label>Status</label>
<select className="form-control">
<option>Select Status</option>
<option>Status 1</option>
<option>Status 2</option>
<option>Status 3</option>
<option>Status 4</option>
<option>Status 5</option>
</select>
</div>
<div className={`col-12 ${isMobile ? 'd-flex justify-content-between' : 'col-md-2 d-flex align-items-end'}`} style={{ gap: '10px' }}>
<button className="btn btn-primary w-48">Apply</button>
<button className="btn btn-secondary w-48">Cancel</button>
</div>
</div>
</div>
{/* Action Buttons */}
<div className="d-flex justify-content-between align-items-center mb-3">
<div>
{buttonData.map((button, index) =>
button.enabled ? (
<button
key={index}
className={`btn btn-light ${isMobile ? 'mb-2' : ''}`} // Add margin on mobile
style={styles.actionButton}
>
{button.label}
</button>
) : null
)}
</div>
{/* Search Bar with Icon */}
<div className="input-group" style={{ width: '250px', display: 'flex', alignItems: 'center', justifyContent: 'flex-end' }}>
<input
type="text"
placeholder="Search..."
className="form-control"
/>
<span className="input-group-text">
<i className="fas fa-search"></i> {/* FontAwesome search icon */}
</span>
</div>
</div>
{/* Table */}
<div className="table-responsive">
<table className="table table-bordered" style={styles.tableContainer}>
<thead>
<tr>
<th>No.</th> {/* Kolom untuk Nomor Urut */}
<th>
<button className="btn" onClick={() => handleSort('transactionId')}>
Transaction ID
{sortConfig.key === 'transactionId' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'transactionId' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('applicationName')}>
Application Name
{sortConfig.key === 'applicationName' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'applicationName' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('referenceId')}>
Reference ID
{sortConfig.key === 'referenceId' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'referenceId' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('dateSent')}>
Date Sent
{sortConfig.key === 'dateSent' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'dateSent' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('mode')}>
Mode
{sortConfig.key === 'mode' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'mode' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('status')}>
Status
{sortConfig.key === 'status' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'status' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
</tr>
</thead>
<tbody>
{paginatedData.length > 0 ? (
paginatedData.map((transaction, index) => (
<tr key={index}>
{/* Kolom Nomor Urut */}
<td>{(currentPage - 1) * dataPerPage + index + 1}</td> {/* Nomor urut berdasarkan halaman dan index */}
<td>{transaction.transactionId}</td>
<td>{transaction.applicationName}</td>
<td>{transaction.referenceId}</td>
<td>{transaction.dateSent}</td>
<td>{transaction.mode}</td>
<td>{transaction.status}</td>
</tr>
))
) : (
<tr>
<td colSpan="7" className="text-center">
<div className="d-flex flex-column align-items-center mt-5">
<img src={NoAvailable} alt="No Data Available" className="mb-3" style={styles.iconStyle} />
<p>Data not available</p>
</div>
</td>
</tr>
)}
</tbody>
</table>
</div>
{/* Pagination */}
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={handlePageChange}
/>
</div>
</div>
);
};
export default Transaction;
const styles = {
contentContainer: {
padding: '20px',
border: '0.1px solid rgba(0, 0, 0, 0.2)',
borderLeft: '4px solid #0542cc',
borderRadius: '10px',
width: '100%',
},
tableContainer: {
minHeight: '300px',
maxHeight: '1500px',
overflowY: 'auto',
},
iconStyle: {
width: '50px',
height: '50px',
},
// Add margin-left style for icons
iconMarginLeft: {
marginLeft: '0.7rem', // Adjust as needed
},
};

View File

@ -0,0 +1,359 @@
import React, { useEffect, useState } from 'react';
import { Link, Routes, Route, useNavigate } from 'react-router-dom';
import Select from 'react-select';
const BASE_URL = process.env.REACT_APP_BASE_URL;
const API_KEY = process.env.REACT_APP_API_KEY;
const Verify = () => {
const [isLoading, setIsLoading] = useState(false);
const [applicationId, setApplicationId] = useState('');
const [inputValueApplication, setInputValueApplication] = useState('');
const [applicationIds, setApplicationIds] = useState([]);
const [selectedQuota, setSelectedQuota] = useState(0);
const [isMobile, setIsMobile] = useState(false);
const [nik, setNik] = useState('');
const [nikError, setNikError] = useState('');
const [date, setDate] = useState('');
const [dateError, setDateError] = useState('');
const [name, setName] = useState('');
const [nameError, setNameError] = useState('');
const [phoneId, setPhoneId] = useState('');
const [phoneError, setPhoneError] = useState('');
const [email, setEmail] = useState('');
const [gender, setGender] = useState('');
const [incomeRange, setIncomeRange] = useState('');
const [companyName, setCompanyName] = useState('');
const [emailError, setEmailError] = useState('');
const [genderError, setGenderError] = useState('');
const [incomeRangeError, setIncomeRangeError] = useState('');
const [companyNameError, setCompanyNameError] = useState('');
// Fungsi untuk menangani perubahan pada NPWP
const handleNikChange = (e) => {
setNik(e.target.value);
};
const handleDateChange = (e) => {
setDate(e.target.value);
};
const handleNameChange = (e) => {
setName(e.target.value);
};
const handleEmailChange = (e) => {
setEmail(e.target.value);
};
// Fungsi untuk menangani perubahan gender
const handleGenderChange = (e) => {
setGender(e.target.value);
};
// Fungsi untuk menangani perubahan income range
const handleIncomeRangeChange = (e) => {
setIncomeRange(e.target.value);
};
// Fungsi untuk menangani perubahan company name
const handleCompanyNameChange = (e) => {
setCompanyName(e.target.value);
};
const applicationOptions = applicationIds.map(app => ({
value: app.id,
label: app.name
}));
const handleApplicationChange = (selectedOption) => {
if (selectedOption) {
const selectedId = selectedOption.value;
const selectedApp = applicationIds.find(app => app.id === parseInt(selectedId));
if (selectedApp) {
setApplicationId(selectedId);
setSelectedQuota(selectedApp.quota); // Set the selected quota
}
}
};
const handleInputChangeApplication = (newInputValue) => {
if (newInputValue.length <= 15) {
setInputValueApplication(newInputValue);
}
};
const fetchApplicationIds = async () => {
try {
const response = await fetch(`${BASE_URL}/application/list`, {
method: 'GET',
headers: {
'accept': 'application/json',
'x-api-key': API_KEY,
},
});
const data = await response.json();
return data.details.data;
} catch (error) {
console.error('Error fetching application IDs:', error);
return [];
}
};
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
const data = await fetchApplicationIds();
setApplicationIds(data);
setIsLoading(false);
};
fetchData();
const checkIfMobile = () => {
setIsMobile(window.innerWidth <= 768);
};
checkIfMobile();
window.addEventListener('resize', checkIfMobile);
return () => window.removeEventListener('resize', checkIfMobile);
}, []);
const handleClick = () => {
setSelectedQuota(prevQuota => prevQuota - 1); // Mengurangi nilai selectedQuota
console.log('Click Check Now!'); // Tampilkan log pada klik
};
const styles = {
container: {
marginTop: '3%',
padding: '0 15px',
},
welcomeCard: {
backgroundColor: '#E2FBEA',
borderLeft: '4px solid #0542CC',
borderRadius: '5px',
marginBottom: '20px',
},
createButton: {
backgroundColor: '#0542CC',
},
section: {
padding: '20px',
border: '0.1px solid rgba(0, 0, 0, 0.2)',
borderLeft: '4px solid #0542CC',
borderRadius: '10px',
width: '100%',
},
tabLink: {
padding: '10px 20px',
},
text: {
fontSize: '16px',
fontWeight: '400',
margin: '0',
marginTop: '8px',
},
remainingQuotaWrapper: {
marginTop: '2vh',
},
remainingQuota: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
marginTop: '4px',
},
quotaText: {
fontSize: '40px',
color: '#0542cc',
fontWeight: '600',
},
timesText: {
marginLeft: '8px',
verticalAlign: 'super',
fontSize: '20px',
},
submitButton: {
marginLeft: 'auto',
marginTop: '4rem',
textAlign: 'start',
position: 'relative',
zIndex: 1,
},
submitBtn: {
backgroundColor: '#0542CC',
},
loadingOverlay: {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.2)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: 1000,
},
spinner: {
border: '4px solid rgba(0, 0, 0, 0.1)',
borderLeftColor: '#0542cc',
borderRadius: '50%',
width: '90px',
height: '90px',
animation: 'spin 1s ease-in-out infinite',
},
select: {
width: '100%',
paddingRight: '30px',
fontSize: '16px',
border: '1px solid #ccc',
outline: 'none',
},
OtpId: {
marginTop: isMobile ? '2vh' : '0',
},
PhoneNumber: {
marginTop: isMobile ? '2vh' : '-12vh',
},
textarea: {
width: '100%',
height: '200px',
resize: 'none',
border: '1px solid #ccc',
outline: 'none',
},
characterCount: {
display: 'flex',
justifyContent: 'flex-end',
marginTop: '8px',
},
};
return (
<div className="container" style={styles.container}>
<style>
{`
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`}
</style>
{isLoading && (
<div style={styles.loadingOverlay}>
<div style={styles.spinner}></div>
</div>
)}
{/* Static Content */}
<div className="row-card border-left border-primary shadow mb-4" style={styles.welcomeCard}>
<div className="d-flex flex-column justify-content-start align-items-start p-4">
<h4 className="mb-3 text-start">
<i className="fas fa-warning fa-bold me-3"></i>Alert
</h4>
<p className="mb-0 text-start">
Get started now by creating an Application ID and explore all the demo services available on the dashboard.
Experience the ease and flexibility of trying out all our features firsthand.
</p>
<div className="d-flex flex-row mt-3">
<Link to="/createApps" style={{ textDecoration: 'none' }}>
<button className="btn d-flex justify-content-center align-items-center me-2" style={styles.createButton}>
<i className="fas fa-plus text-white me-2"></i>
<p className="text-white mb-0">Create New App ID</p>
</button>
</Link>
</div>
</div>
</div>
{/* Tab Navigation */}
<div style={styles.section}>
<div className="form-group row align-items-center">
<div className="col-md-6">
<Select
id="applicationId"
value={applicationOptions.find(option => option.value === applicationId)}
onChange={handleApplicationChange}
options={applicationOptions}
placeholder="Select Application ID"
isSearchable
menuPortalTarget={document.body}
menuPlacement="auto"
inputValue={inputValueApplication}
onInputChange={handleInputChangeApplication}
/>
</div>
<div className="col-md-6" style={styles.remainingQuotaWrapper}>
<p style={styles.text}>Remaining Quota</p>
<div style={styles.remainingQuota}>
<span style={styles.quotaText}>{selectedQuota}</span>
<span style={styles.timesText}>(times)</span>
</div>
</div>
</div>
{/* Nik and Date Row */}
<div className="form-group row align-items-center">
{/* Kolom untuk input email */}
<div className="col-md-6 mt-4">
<div className="input-wrapper">
<input
type="nik"
id="nik"
className="form-control"
value={nik}
onChange={handleNikChange}
placeholder="NIK"
/>
</div>
{/* Menampilkan error jika ada */}
{nikError && <small style={styles.SelfieImageError}>{nikError}</small>}
</div>
{/* Kolom untuk input tanggal */}
<div className="col-md-6">
<div className="input-wrapper">
<label htmlFor="date" style={{ display: 'block', marginBottom: '5px' }}>
Date of Birth
</label>
<input
type="date"
id="date"
className="form-control"
value={date}
onChange={handleDateChange}
/>
</div>
{/* Menampilkan error jika ada */}
{dateError && <small style={styles.SelfieImageError}>{dateError}</small>}
</div>
</div>
{/* Name and Phone */}
<div className="form-group row align-items-center my-4">
{/* Kolom untuk input name */}
<div className="col-md-6">
<div className="input-wrapper">
<input
type="name"
id="name"
className="form-control"
value={name}
onChange={handleNameChange}
placeholder="Name"
/>
</div>
{/* Menampilkan error jika ada */}
{nameError && <small style={styles.SelfieImageError}>{nameError}</small>}
</div>
</div>
<div style={styles.submitButton}>
<button onClick={handleClick} className="btn d-flex justify-content-center align-items-center me-2" style={styles.submitBtn}>
<p className="text-white mb-0">Check Now</p>
</button>
</div>
</div>
</div>
);
};
export default Verify;

View File

@ -0,0 +1,7 @@
import Verify from "./Verify";
import Transaction from './Transaction';
export {
Verify,
Transaction
}

View File

@ -0,0 +1,411 @@
import React, { useState, useEffect } from 'react';
import { FaChevronLeft, FaChevronRight, FaFastBackward, FaFastForward, FaSort, FaSortUp, FaSortDown } from 'react-icons/fa'; // Icons for sorting
import { NoAvailable } from '../../../assets/icon';
// Pagination Component
const Pagination = ({ currentPage, totalPages, onPageChange }) => {
const handlePrev = () => {
if (currentPage > 1) {
onPageChange(currentPage - 1);
}
};
const handleNext = () => {
if (currentPage < totalPages) {
onPageChange(currentPage + 1);
}
};
const handleFirst = () => {
onPageChange(1); // Go to first page
};
const handleLast = () => {
onPageChange(totalPages); // Go to last page
};
// Logic to display only 3 pages in pagination
const getPaginationRange = () => {
const range = [];
const totalPagesCount = totalPages;
let start = currentPage - 1;
let end = currentPage + 1;
// Adjust start and end if near the boundaries
if (currentPage === 1) {
start = 1;
end = Math.min(3, totalPagesCount);
} else if (currentPage === totalPages) {
start = Math.max(totalPagesCount - 2, 1);
end = totalPagesCount;
}
for (let i = start; i <= end; i++) {
range.push(i);
}
return range;
};
const pageRange = getPaginationRange();
return (
<div className="pagination-container d-flex justify-content-end mt-4">
{/* First Page Button */}
<button
className="btn"
onClick={handleFirst}
disabled={currentPage === 1}
>
<FaFastBackward /> {/* Double Arrow Left */}
</button>
<button
className="btn"
onClick={handlePrev}
disabled={currentPage === 1}
>
<FaChevronLeft /> {/* Single Arrow Left */}
</button>
{/* Page Numbers */}
{pageRange.map((pageNum) => (
<button
key={pageNum}
className={`btn ${pageNum === currentPage ? 'btn-primary' : ''}`}
onClick={() => onPageChange(pageNum)}
>
{pageNum}
</button>
))}
<button
className="btn"
onClick={handleNext}
disabled={currentPage === totalPages}
>
<FaChevronRight /> {/* Single Arrow Right */}
</button>
{/* Last Page Button */}
<button
className="btn"
onClick={handleLast}
disabled={currentPage === totalPages}
>
<FaFastForward /> {/* Double Arrow Right */}
</button>
</div>
);
};
const Transaction = () => {
const [currentPage, setCurrentPage] = useState(1);
const [isMobile, setIsMobile] = useState(false); // State to detect mobile view
const [transactionData, setTransactionData] = useState([]);
const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc' }); // Sorting state
const dataPerPage = 10; // Data per page (10 data per page)
const buttonData = [
{ label: 'Copy', enabled: true },
{ label: 'CSV', enabled: true },
{ label: 'Excel', enabled: true },
{ label: 'PDF', enabled: true },
{ label: 'Print', enabled: true },
{ label: 'Column Visibility', enabled: true },
];
// Generate 691 dummy transactions
const generateDummyData = (numOfItems) => {
const transactionData = [];
for (let i = 1; i <= numOfItems; i++) {
transactionData.push({
transactionId: `TX${String(i).padStart(3, '0')}`, // Transaction ID
applicationName: `App ${Math.floor(Math.random() * 5) + 1}`, // Application Name
referenceId: `REF${String(i).padStart(3, '0')}`, // Reference ID
dateSent: new Date(2023, Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1).toLocaleDateString(), // Date Sent (random date)
mode: Math.random() > 0.5 ? 'Online' : 'Offline', // Method (randomly Online or Offline)
status: ['Completed', 'Pending', 'Failed'][Math.floor(Math.random() * 3)], // Status acak
});
}
return transactionData;
};
// Set the generated transaction data
useEffect(() => {
setTransactionData(generateDummyData(122)); // count data dummy transactions
}, []);
// Sorting function
const sortData = (data, config) => {
const { key, direction } = config;
return [...data].sort((a, b) => {
if (a[key] < b[key]) {
return direction === 'asc' ? -1 : 1;
}
if (a[key] > b[key]) {
return direction === 'asc' ? 1 : -1;
}
return 0;
});
};
// Handle column header sort click
const handleSort = (key) => {
let direction = 'asc';
if (sortConfig.key === key && sortConfig.direction === 'asc') {
direction = 'desc'; // Toggle direction if the same column is clicked
}
setSortConfig({ key, direction });
};
// Get the paginated data
const getPaginatedData = (data, page, perPage) => {
const sortedData = sortData(data, sortConfig);
const startIndex = (page - 1) * perPage;
const endIndex = startIndex + perPage;
return sortedData.slice(startIndex, endIndex);
};
// Handle page change
const handlePageChange = (page) => {
setCurrentPage(page);
};
// Calculate total pages based on the data and data per page
const totalPages = Math.ceil(transactionData.length / dataPerPage);
// Paginated data
const paginatedData = getPaginatedData(transactionData, currentPage, dataPerPage);
// Detect screen size and update isMobile state
useEffect(() => {
const handleResize = () => {
setIsMobile(window.innerWidth <= 768); // Change 768 to your breakpoint
};
handleResize();
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return (
<div className="container mt-5">
{/* Welcome Message */}
<div className="row-card border-left border-primary shadow mb-4" style={{ backgroundColor: '#E2FBEA' }}>
<div className="d-flex flex-column justify-content-start align-items-start p-4">
<div>
<h4 className="mb-3 text-start">
<i className="fas fa-warning fa-bold me-3"></i>Alert
</h4>
<p className="mb-0 text-start">
Get started now by creating an Application ID and explore all the demo services available on the dashboard.
Experience the ease and flexibility of trying out all our features firsthand.
</p>
</div>
</div>
</div>
<div style={styles.contentContainer}>
{/* Filter Form */}
<div className="card p-3 mb-4">
<div className="row">
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<label>Start Date</label>
<input type="date" className="form-control" />
</div>
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<label>End Date</label>
<input type="date" className="form-control" />
</div>
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<label>Application</label>
<select className="form-control">
<option>Select Application</option>
<option>App 1</option>
<option>App 2</option>
<option>App 3</option>
<option>App 4</option>
<option>App 5</option>
</select>
</div>
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<label>Status</label>
<select className="form-control">
<option>Select Status</option>
<option>Status 1</option>
<option>Status 2</option>
<option>Status 3</option>
<option>Status 4</option>
<option>Status 5</option>
</select>
</div>
<div className={`col-12 ${isMobile ? 'd-flex justify-content-between' : 'col-md-2 d-flex align-items-end'}`} style={{ gap: '10px' }}>
<button className="btn btn-primary w-48">Apply</button>
<button className="btn btn-secondary w-48">Cancel</button>
</div>
</div>
</div>
{/* Action Buttons */}
<div className="d-flex justify-content-between align-items-center mb-3">
<div>
{buttonData.map((button, index) =>
button.enabled ? (
<button
key={index}
className={`btn btn-light ${isMobile ? 'mb-2' : ''}`} // Add margin on mobile
style={styles.actionButton}
>
{button.label}
</button>
) : null
)}
</div>
{/* Search Bar with Icon */}
<div className="input-group" style={{ width: '250px', display: 'flex', alignItems: 'center', justifyContent: 'flex-end' }}>
<input
type="text"
placeholder="Search..."
className="form-control"
/>
<span className="input-group-text">
<i className="fas fa-search"></i> {/* FontAwesome search icon */}
</span>
</div>
</div>
{/* Table */}
<div className="table-responsive">
<table className="table table-bordered" style={styles.tableContainer}>
<thead>
<tr>
<th>No.</th> {/* Kolom untuk Nomor Urut */}
<th>
<button className="btn" onClick={() => handleSort('transactionId')}>
Transaction ID
{sortConfig.key === 'transactionId' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'transactionId' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('applicationName')}>
Application Name
{sortConfig.key === 'applicationName' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'applicationName' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('referenceId')}>
Reference ID
{sortConfig.key === 'referenceId' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'referenceId' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('dateSent')}>
Date Sent
{sortConfig.key === 'dateSent' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'dateSent' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('mode')}>
Mode
{sortConfig.key === 'mode' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'mode' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('status')}>
Status
{sortConfig.key === 'status' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'status' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
</tr>
</thead>
<tbody>
{paginatedData.length > 0 ? (
paginatedData.map((transaction, index) => (
<tr key={index}>
{/* Kolom Nomor Urut */}
<td>{(currentPage - 1) * dataPerPage + index + 1}</td> {/* Nomor urut berdasarkan halaman dan index */}
<td>{transaction.transactionId}</td>
<td>{transaction.applicationName}</td>
<td>{transaction.referenceId}</td>
<td>{transaction.dateSent}</td>
<td>{transaction.mode}</td>
<td>{transaction.status}</td>
</tr>
))
) : (
<tr>
<td colSpan="7" className="text-center">
<div className="d-flex flex-column align-items-center mt-5">
<img src={NoAvailable} alt="No Data Available" className="mb-3" style={styles.iconStyle} />
<p>Data not available</p>
</div>
</td>
</tr>
)}
</tbody>
</table>
</div>
{/* Pagination */}
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={handlePageChange}
/>
</div>
</div>
);
};
export default Transaction;
const styles = {
contentContainer: {
padding: '20px',
border: '0.1px solid rgba(0, 0, 0, 0.2)',
borderLeft: '4px solid #0542cc',
borderRadius: '10px',
width: '100%',
},
tableContainer: {
minHeight: '300px',
maxHeight: '1500px',
overflowY: 'auto',
},
iconStyle: {
width: '50px',
height: '50px',
},
// Add margin-left style for icons
iconMarginLeft: {
marginLeft: '0.7rem', // Adjust as needed
},
};

View File

@ -0,0 +1,449 @@
import React, { useEffect, useState } from 'react';
import { Link, Routes, Route, useNavigate } from 'react-router-dom';
import Select from 'react-select';
const BASE_URL = process.env.REACT_APP_BASE_URL;
const API_KEY = process.env.REACT_APP_API_KEY;
const Verify = () => {
const [isLoading, setIsLoading] = useState(false);
const [applicationId, setApplicationId] = useState('');
const [inputValueApplication, setInputValueApplication] = useState('');
const [applicationIds, setApplicationIds] = useState([]);
const [selectedQuota, setSelectedQuota] = useState(0);
const [isMobile, setIsMobile] = useState(false);
const [nik, setNik] = useState('');
const [nikError, setNikError] = useState('');
const [date, setDate] = useState('');
const [dateError, setDateError] = useState('');
const [name, setName] = useState('');
const [nameError, setNameError] = useState('');
const [phoneId, setPhoneId] = useState('');
const [phoneError, setPhoneError] = useState('');
const [email, setEmail] = useState('');
const [gender, setGender] = useState('');
const [incomeRange, setIncomeRange] = useState('');
const [companyName, setCompanyName] = useState('');
const [emailError, setEmailError] = useState('');
const [genderError, setGenderError] = useState('');
const [incomeRangeError, setIncomeRangeError] = useState('');
const [companyNameError, setCompanyNameError] = useState('');
// Fungsi untuk menangani perubahan pada NPWP
const handleNikChange = (e) => {
setNik(e.target.value);
};
const handleDateChange = (e) => {
setDate(e.target.value);
};
const handleNameChange = (e) => {
setName(e.target.value);
};
const handleEmailChange = (e) => {
setEmail(e.target.value);
};
// Fungsi untuk menangani perubahan gender
const handleGenderChange = (e) => {
setGender(e.target.value);
};
// Fungsi untuk menangani perubahan income range
const handleIncomeRangeChange = (e) => {
setIncomeRange(e.target.value);
};
// Fungsi untuk menangani perubahan company name
const handleCompanyNameChange = (e) => {
setCompanyName(e.target.value);
};
const applicationOptions = applicationIds.map(app => ({
value: app.id,
label: app.name
}));
const handleApplicationChange = (selectedOption) => {
if (selectedOption) {
const selectedId = selectedOption.value;
const selectedApp = applicationIds.find(app => app.id === parseInt(selectedId));
if (selectedApp) {
setApplicationId(selectedId);
setSelectedQuota(selectedApp.quota); // Set the selected quota
}
}
};
const handleInputChangeApplication = (newInputValue) => {
if (newInputValue.length <= 15) {
setInputValueApplication(newInputValue);
}
};
const fetchApplicationIds = async () => {
try {
const response = await fetch(`${BASE_URL}/application/list`, {
method: 'GET',
headers: {
'accept': 'application/json',
'x-api-key': API_KEY,
},
});
const data = await response.json();
return data.details.data;
} catch (error) {
console.error('Error fetching application IDs:', error);
return [];
}
};
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
const data = await fetchApplicationIds();
setApplicationIds(data);
setIsLoading(false);
};
fetchData();
const checkIfMobile = () => {
setIsMobile(window.innerWidth <= 768);
};
checkIfMobile();
window.addEventListener('resize', checkIfMobile);
return () => window.removeEventListener('resize', checkIfMobile);
}, []);
const handleClick = () => {
setSelectedQuota(prevQuota => prevQuota - 1); // Mengurangi nilai selectedQuota
console.log('Click Check Now!'); // Tampilkan log pada klik
};
const styles = {
container: {
marginTop: '3%',
padding: '0 15px',
},
welcomeCard: {
backgroundColor: '#E2FBEA',
borderLeft: '4px solid #0542CC',
borderRadius: '5px',
marginBottom: '20px',
},
createButton: {
backgroundColor: '#0542CC',
},
section: {
padding: '20px',
border: '0.1px solid rgba(0, 0, 0, 0.2)',
borderLeft: '4px solid #0542CC',
borderRadius: '10px',
width: '100%',
},
tabLink: {
padding: '10px 20px',
},
text: {
fontSize: '16px',
fontWeight: '400',
margin: '0',
marginTop: '8px',
},
remainingQuotaWrapper: {
marginTop: '2vh',
},
remainingQuota: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
marginTop: '4px',
},
quotaText: {
fontSize: '40px',
color: '#0542cc',
fontWeight: '600',
},
timesText: {
marginLeft: '8px',
verticalAlign: 'super',
fontSize: '20px',
},
submitButton: {
marginLeft: 'auto',
marginTop: '4rem',
textAlign: 'start',
position: 'relative',
zIndex: 1,
},
submitBtn: {
backgroundColor: '#0542CC',
},
loadingOverlay: {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.2)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: 1000,
},
spinner: {
border: '4px solid rgba(0, 0, 0, 0.1)',
borderLeftColor: '#0542cc',
borderRadius: '50%',
width: '90px',
height: '90px',
animation: 'spin 1s ease-in-out infinite',
},
select: {
width: '100%',
paddingRight: '30px',
fontSize: '16px',
border: '1px solid #ccc',
outline: 'none',
},
OtpId: {
marginTop: isMobile ? '2vh' : '0',
},
PhoneNumber: {
marginTop: isMobile ? '2vh' : '-12vh',
},
textarea: {
width: '100%',
height: '200px',
resize: 'none',
border: '1px solid #ccc',
outline: 'none',
},
characterCount: {
display: 'flex',
justifyContent: 'flex-end',
marginTop: '8px',
},
};
return (
<div className="container" style={styles.container}>
<style>
{`
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`}
</style>
{isLoading && (
<div style={styles.loadingOverlay}>
<div style={styles.spinner}></div>
</div>
)}
{/* Static Content */}
<div className="row-card border-left border-primary shadow mb-4" style={styles.welcomeCard}>
<div className="d-flex flex-column justify-content-start align-items-start p-4">
<h4 className="mb-3 text-start">
<i className="fas fa-warning fa-bold me-3"></i>Alert
</h4>
<p className="mb-0 text-start">
Get started now by creating an Application ID and explore all the demo services available on the dashboard.
Experience the ease and flexibility of trying out all our features firsthand.
</p>
<div className="d-flex flex-row mt-3">
<Link to="/createApps" style={{ textDecoration: 'none' }}>
<button className="btn d-flex justify-content-center align-items-center me-2" style={styles.createButton}>
<i className="fas fa-plus text-white me-2"></i>
<p className="text-white mb-0">Create New App ID</p>
</button>
</Link>
</div>
</div>
</div>
{/* Tab Navigation */}
<div style={styles.section}>
<div className="form-group row align-items-center">
<div className="col-md-6">
<Select
id="applicationId"
value={applicationOptions.find(option => option.value === applicationId)}
onChange={handleApplicationChange}
options={applicationOptions}
placeholder="Select Application ID"
isSearchable
menuPortalTarget={document.body}
menuPlacement="auto"
inputValue={inputValueApplication}
onInputChange={handleInputChangeApplication}
/>
</div>
<div className="col-md-6" style={styles.remainingQuotaWrapper}>
<p style={styles.text}>Remaining Quota</p>
<div style={styles.remainingQuota}>
<span style={styles.quotaText}>{selectedQuota}</span>
<span style={styles.timesText}>(times)</span>
</div>
</div>
</div>
{/* Nik and Date Row */}
<div className="form-group row align-items-center">
{/* Kolom untuk input email */}
<div className="col-md-6">
<div className="input-wrapper">
<input
type="nik"
id="nik"
className="form-control"
value={nik}
onChange={handleNikChange}
placeholder="NIK"
/>
</div>
{/* Menampilkan error jika ada */}
{nikError && <small style={styles.SelfieImageError}>{nikError}</small>}
</div>
{/* Kolom untuk input tanggal */}
<div className="col-md-6">
<div className="input-wrapper">
<input
type="date"
id="date"
className="form-control"
value={date}
onChange={handleDateChange}
/>
</div>
{/* Menampilkan error jika ada */}
{dateError && <small style={styles.SelfieImageError}>{dateError}</small>}
</div>
</div>
{/* Name and Phone */}
<div className="form-group row align-items-center my-4">
{/* Kolom untuk input name */}
<div className="col-md-6">
<div className="input-wrapper">
<input
type="name"
id="name"
className="form-control"
value={name}
onChange={handleNameChange}
placeholder="Name"
/>
</div>
{/* Menampilkan error jika ada */}
{nameError && <small style={styles.SelfieImageError}>{nameError}</small>}
</div>
<div className="col-md-6">
<div className="input-group">
<span className="input-group-prepend">
<span className="input-group-text">+62</span>
</span>
<input
type="text"
id="phoneId"
className="form-control"
placeholder="Phone Number"
value={phoneId}
onChange={(e) => setPhoneId(e.target.value)}
/>
</div>
{/* Menampilkan error jika ada */}
{phoneError && <small style={styles.SelfieImageError}>{phoneError}</small>}
</div>
</div>
{/* Email and Gender */}
<div className="form-group row align-items-center my-4">
{/* Kolom untuk input email */}
<div className="col-md-6">
<div className="input-wrapper">
<input
type="email" // Mengubah tipe input menjadi email
id="email"
className="form-control"
value={email}
onChange={handleEmailChange} // Fungsi untuk menangani perubahan input email
placeholder="Email"
/>
</div>
{/* Menampilkan error jika ada */}
{emailError && <small style={styles.SelfieImageError}>{emailError}</small>}
</div>
{/* Kolom untuk Select Gender */}
<div className="col-md-6">
<div className="input-wrapper">
<select
id="gender"
className="form-control"
value={gender} // State untuk gender
onChange={handleGenderChange} // Fungsi untuk menangani perubahan pilihan gender
>
<option value="">Select Gender</option> {/* Placeholder option */}
<option value="Male">Male</option> {/* Pilihan Male */}
<option value="Female">Female</option> {/* Pilihan Female */}
<option value="Other">Other</option> {/* Pilihan Other */}
</select>
</div>
{/* Menampilkan error jika ada */}
{genderError && <small style={styles.SelfieImageError}>{genderError}</small>}
</div>
</div>
<div className="form-group row align-items-center my-4">
{/* Kolom untuk Select Income Range */}
<div className="col-md-6">
<div className="input-wrapper">
<select
id="incomeRange"
className="form-control"
value={incomeRange} // State untuk income range
onChange={handleIncomeRangeChange} // Fungsi untuk menangani perubahan income range
>
<option value="">Select Income Range</option>
<option value="0-50k">0 - 50k</option>
<option value="51k-100k">51k - 100k</option>
<option value="101k-200k">101k - 200k</option>
<option value="201k+">201k+</option>
</select>
</div>
{incomeRangeError && <small style={styles.SelfieImageError}>{incomeRangeError}</small>}
</div>
{/* Kolom untuk input Company Name */}
<div className="col-md-6">
<div className="input-wrapper">
<input
type="text"
id="companyName"
className="form-control"
value={companyName}
onChange={handleCompanyNameChange} // Fungsi untuk menangani perubahan input company name
placeholder="Company Name"
/>
</div>
{companyNameError && <small style={styles.SelfieImageError}>{companyNameError}</small>}
</div>
</div>
<div style={styles.submitButton}>
<button onClick={handleClick} className="btn d-flex justify-content-center align-items-center me-2" style={styles.submitBtn}>
<p className="text-white mb-0">Check Now</p>
</button>
</div>
</div>
</div>
);
};
export default Verify;

View File

@ -0,0 +1,7 @@
import Verify from "./Verify";
import Transaction from './Transaction';
export {
Verify,
Transaction
}

View File

@ -0,0 +1,400 @@
import React, { useState, useEffect } from 'react';
import { FaChevronLeft, FaChevronRight, FaFastBackward, FaFastForward, FaSort, FaSortUp, FaSortDown } from 'react-icons/fa'; // Icons for sorting
import { NoAvailable } from '../../../assets/icon';
// Pagination Component
const Pagination = ({ currentPage, totalPages, onPageChange }) => {
const handlePrev = () => {
if (currentPage > 1) {
onPageChange(currentPage - 1);
}
};
const handleNext = () => {
if (currentPage < totalPages) {
onPageChange(currentPage + 1);
}
};
const handleFirst = () => {
onPageChange(1); // Go to first page
};
const handleLast = () => {
onPageChange(totalPages); // Go to last page
};
// Logic to display only 3 pages in pagination
const getPaginationRange = () => {
const range = [];
const totalPagesCount = totalPages;
let start = currentPage - 1;
let end = currentPage + 1;
// Adjust start and end if near the boundaries
if (currentPage === 1) {
start = 1;
end = Math.min(3, totalPagesCount);
} else if (currentPage === totalPages) {
start = Math.max(totalPagesCount - 2, 1);
end = totalPagesCount;
}
for (let i = start; i <= end; i++) {
range.push(i);
}
return range;
};
const pageRange = getPaginationRange();
return (
<div className="pagination-container d-flex justify-content-end mt-4">
{/* First Page Button */}
<button
className="btn"
onClick={handleFirst}
disabled={currentPage === 1}
>
<FaFastBackward /> {/* Double Arrow Left */}
</button>
<button
className="btn"
onClick={handlePrev}
disabled={currentPage === 1}
>
<FaChevronLeft /> {/* Single Arrow Left */}
</button>
{/* Page Numbers */}
{pageRange.map((pageNum) => (
<button
key={pageNum}
className={`btn ${pageNum === currentPage ? 'btn-primary' : ''}`}
onClick={() => onPageChange(pageNum)}
>
{pageNum}
</button>
))}
<button
className="btn"
onClick={handleNext}
disabled={currentPage === totalPages}
>
<FaChevronRight /> {/* Single Arrow Right */}
</button>
{/* Last Page Button */}
<button
className="btn"
onClick={handleLast}
disabled={currentPage === totalPages}
>
<FaFastForward /> {/* Double Arrow Right */}
</button>
</div>
);
};
const Transaction = () => {
const [currentPage, setCurrentPage] = useState(1);
const [isMobile, setIsMobile] = useState(false); // State to detect mobile view
const [transactionData, setTransactionData] = useState([]);
const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc' }); // Sorting state
const dataPerPage = 10; // Data per page (10 data per page)
const buttonData = [
{ label: 'Copy', enabled: true },
{ label: 'CSV', enabled: true },
{ label: 'Excel', enabled: true },
{ label: 'PDF', enabled: true },
{ label: 'Print', enabled: true },
{ label: 'Column Visibility', enabled: true },
];
// Generate 691 dummy transactions
const generateDummyData = (numOfItems) => {
const transactionData = [];
for (let i = 1; i <= numOfItems; i++) {
transactionData.push({
transactionId: `TX${String(i).padStart(3, '0')}`, // Transaction ID
applicationName: `App ${Math.floor(Math.random() * 5) + 1}`, // Application Name
referenceId: `REF${String(i).padStart(3, '0')}`, // Reference ID
dateSent: new Date(2023, Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1).toLocaleDateString(), // Date Sent (random date)
status: ['Completed', 'Pending', 'Failed'][Math.floor(Math.random() * 3)], // Status acak
});
}
return transactionData;
};
// Set the generated transaction data
useEffect(() => {
setTransactionData(generateDummyData(122)); // count data dummy transactions
}, []);
// Sorting function
const sortData = (data, config) => {
const { key, direction } = config;
return [...data].sort((a, b) => {
if (a[key] < b[key]) {
return direction === 'asc' ? -1 : 1;
}
if (a[key] > b[key]) {
return direction === 'asc' ? 1 : -1;
}
return 0;
});
};
// Handle column header sort click
const handleSort = (key) => {
let direction = 'asc';
if (sortConfig.key === key && sortConfig.direction === 'asc') {
direction = 'desc'; // Toggle direction if the same column is clicked
}
setSortConfig({ key, direction });
};
// Get the paginated data
const getPaginatedData = (data, page, perPage) => {
const sortedData = sortData(data, sortConfig);
const startIndex = (page - 1) * perPage;
const endIndex = startIndex + perPage;
return sortedData.slice(startIndex, endIndex);
};
// Handle page change
const handlePageChange = (page) => {
setCurrentPage(page);
};
// Calculate total pages based on the data and data per page
const totalPages = Math.ceil(transactionData.length / dataPerPage);
// Paginated data
const paginatedData = getPaginatedData(transactionData, currentPage, dataPerPage);
// Detect screen size and update isMobile state
useEffect(() => {
const handleResize = () => {
setIsMobile(window.innerWidth <= 768); // Change 768 to your breakpoint
};
handleResize();
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return (
<div className="container mt-5">
{/* Welcome Message */}
<div className="row-card border-left border-primary shadow mb-4" style={{ backgroundColor: '#E2FBEA' }}>
<div className="d-flex flex-column justify-content-start align-items-start p-4">
<div>
<h4 className="mb-3 text-start">
<i className="fas fa-warning fa-bold me-3"></i>Alert
</h4>
<p className="mb-0 text-start">
Get started now by creating an Application ID and explore all the demo services available on the dashboard.
Experience the ease and flexibility of trying out all our features firsthand.
</p>
</div>
</div>
</div>
<div style={styles.contentContainer}>
{/* Filter Form */}
<div className="card p-3 mb-4">
<div className="row">
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<label>Start Date</label>
<input type="date" className="form-control" />
</div>
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<label>End Date</label>
<input type="date" className="form-control" />
</div>
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<label>Application</label>
<select className="form-control">
<option>Select Application</option>
<option>App 1</option>
<option>App 2</option>
<option>App 3</option>
<option>App 4</option>
<option>App 5</option>
</select>
</div>
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<label>Status</label>
<select className="form-control">
<option>Select Status</option>
<option>Status 1</option>
<option>Status 2</option>
<option>Status 3</option>
<option>Status 4</option>
<option>Status 5</option>
</select>
</div>
<div className={`col-12 ${isMobile ? 'd-flex justify-content-between' : 'col-md-2 d-flex align-items-end'}`} style={{ gap: '10px' }}>
<button className="btn btn-primary w-48">Apply</button>
<button className="btn btn-secondary w-48">Cancel</button>
</div>
</div>
</div>
{/* Action Buttons */}
<div className="d-flex justify-content-between align-items-center mb-3">
<div>
{buttonData.map((button, index) =>
button.enabled ? (
<button
key={index}
className={`btn btn-light ${isMobile ? 'mb-2' : ''}`} // Add margin on mobile
style={styles.actionButton}
>
{button.label}
</button>
) : null
)}
</div>
{/* Search Bar with Icon */}
<div className="input-group" style={{ width: '250px', display: 'flex', alignItems: 'center', justifyContent: 'flex-end' }}>
<input
type="text"
placeholder="Search..."
className="form-control"
/>
<span className="input-group-text">
<i className="fas fa-search"></i> {/* FontAwesome search icon */}
</span>
</div>
</div>
{/* Table */}
<div className="table-responsive">
<table className="table table-bordered" style={styles.tableContainer}>
<thead>
<tr>
<th>No.</th> {/* Kolom untuk Nomor Urut */}
<th>
<button className="btn" onClick={() => handleSort('transactionId')}>
Transaction ID
{sortConfig.key === 'transactionId' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'transactionId' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('applicationName')}>
Application Name
{sortConfig.key === 'applicationName' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'applicationName' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('referenceId')}>
Reference ID
{sortConfig.key === 'referenceId' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'referenceId' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('dateSent')}>
Date Sent
{sortConfig.key === 'dateSent' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'dateSent' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('status')}>
Status
{sortConfig.key === 'status' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'status' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
</tr>
</thead>
<tbody>
{paginatedData.length > 0 ? (
paginatedData.map((transaction, index) => (
<tr key={index}>
{/* Kolom Nomor Urut */}
<td>{(currentPage - 1) * dataPerPage + index + 1}</td> {/* Nomor urut berdasarkan halaman dan index */}
<td>{transaction.transactionId}</td>
<td>{transaction.applicationName}</td>
<td>{transaction.referenceId}</td>
<td>{transaction.dateSent}</td>
<td>{transaction.status}</td>
</tr>
))
) : (
<tr>
<td colSpan="7" className="text-center">
<div className="d-flex flex-column align-items-center mt-5">
<img src={NoAvailable} alt="No Data Available" className="mb-3" style={styles.iconStyle} />
<p>Data not available</p>
</div>
</td>
</tr>
)}
</tbody>
</table>
</div>
{/* Pagination */}
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={handlePageChange}
/>
</div>
</div>
);
};
export default Transaction;
const styles = {
contentContainer: {
padding: '20px',
border: '0.1px solid rgba(0, 0, 0, 0.2)',
borderLeft: '4px solid #0542cc',
borderRadius: '10px',
width: '100%',
},
tableContainer: {
minHeight: '300px',
maxHeight: '1500px',
overflowY: 'auto',
},
iconStyle: {
width: '50px',
height: '50px',
},
// Add margin-left style for icons
iconMarginLeft: {
marginLeft: '0.7rem', // Adjust as needed
},
};

View File

@ -0,0 +1,5 @@
import Transaction from './Transaction';
export {
Transaction
}

View File

@ -0,0 +1,288 @@
import React, { useState, useEffect } from 'react';
import Select from 'react-select';
const BASE_URL = process.env.REACT_APP_BASE_URL;
const API_KEY = process.env.REACT_APP_API_KEY;
const Nik = () => {
const [isLoading, setIsLoading] = useState(false);
const [applicationId, setApplicationId] = useState('');
const [messageId, setMessageId] = useState('');
const [inputValueApplication, setInputValueApplication] = useState('');
const [applicationIds, setApplicationIds] = useState([]);
const [isMobile, setIsMobile] = useState(false);
const [nik, setNik] = useState('');
const [selectedQuota, setSelectedQuota] = useState(0);
const [userContentId, setUserContentId] = useState('');
const [inputValueUserContent, setInputValueUserContent] = useState('');
const [userContentOptions, setUserContentOptions] = useState([
{ value: 'content1', label: 'Content 1' },
{ value: 'content2', label: 'Content 2' },
{ value: 'content3', label: 'Content 3' },
]);
// Fungsi untuk menangani perubahan pada NIK
const handleNikChange = (e) => {
setNik(e.target.value);
};
// Fungsi untuk menangani perubahan pada User Content
const handleUserContentChange = (selectedOption) => {
setUserContentId(selectedOption.value);
};
// Fungsi untuk menangani perubahan input di select User Content (untuk pencarian)
const handleInputChangeUserContent = (inputValue) => {
setInputValueUserContent(inputValue);
};
const applicationOptions = applicationIds.map(app => ({
value: app.id,
label: app.name
}));
const handleApplicationChange = (selectedOption) => {
if (selectedOption) {
const selectedId = selectedOption.value;
const selectedApp = applicationIds.find(app => app.id === parseInt(selectedId));
if (selectedApp) {
setApplicationId(selectedId);
setSelectedQuota(selectedApp.quota); // Set the selected quota
}
}
};
const handleInputChangeApplication = (newInputValue) => {
if (newInputValue.length <= 15) {
setInputValueApplication(newInputValue);
}
};
const fetchApplicationIds = async () => {
try {
const response = await fetch(`${BASE_URL}/application/list`, {
method: 'GET',
headers: {
'accept': 'application/json',
'x-api-key': API_KEY,
},
});
const data = await response.json();
return data.details.data;
} catch (error) {
console.error('Error fetching application IDs:', error);
return [];
}
};
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
const data = await fetchApplicationIds();
setApplicationIds(data);
setIsLoading(false);
};
fetchData();
const checkIfMobile = () => {
setIsMobile(window.innerWidth <= 768);
};
checkIfMobile();
window.addEventListener('resize', checkIfMobile);
return () => window.removeEventListener('resize', checkIfMobile);
}, []);
const handleClick = () => {
setSelectedQuota(prevQuota => prevQuota - 1); // Mengurangi nilai selectedQuota
console.log('Click Check Now!'); // Tampilkan log pada klik
};
const styles = {
text: {
fontSize: '16px',
fontWeight: '400',
margin: '0',
marginTop: '8px',
},
remainingQuotaWrapper: {
marginTop: '2vh',
},
remainingQuota: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
marginTop: '4px',
},
quotaText: {
fontSize: '40px',
color: '#0542cc',
fontWeight: '600',
},
timesText: {
marginLeft: '8px',
verticalAlign: 'super',
fontSize: '20px',
},
submitButton: {
marginLeft: 'auto',
marginTop: '4rem',
textAlign: 'start',
position: 'relative',
zIndex: 1,
},
submitBtn: {
backgroundColor: '#0542CC',
},
loadingOverlay: {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.2)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: 1000,
},
spinner: {
border: '4px solid rgba(0, 0, 0, 0.1)',
borderLeftColor: '#0542cc',
borderRadius: '50%',
width: '90px',
height: '90px',
animation: 'spin 1s ease-in-out infinite',
},
select: {
width: '100%',
paddingRight: '30px',
fontSize: '16px',
border: '1px solid #ccc',
outline: 'none',
},
OtpId: {
marginTop: isMobile ? '2vh' : '0',
},
PhoneNumber: {
marginTop: isMobile ? '2vh' : '-12vh',
},
textarea: {
width: '100%',
height: '200px',
resize: 'none',
border: '1px solid #ccc',
outline: 'none',
},
characterCount: {
display: 'flex',
justifyContent: 'flex-end',
marginTop: '8px',
},
};
return (
<div>
<style>
{`
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`}
</style>
{isLoading && (
<div style={styles.loadingOverlay}>
<div style={styles.spinner}></div>
</div>
)}
<div className="form-group row align-items-center">
<div className="col-md-6">
<Select
id="applicationId"
value={applicationOptions.find(option => option.value === applicationId)}
onChange={handleApplicationChange}
options={applicationOptions}
placeholder="Select Application ID"
isSearchable
menuPortalTarget={document.body}
menuPlacement="auto"
inputValue={inputValueApplication}
onInputChange={handleInputChangeApplication}
/>
</div>
<div className="col-md-6" style={styles.remainingQuotaWrapper}>
<p style={styles.text}>Remaining Quota</p>
<div style={styles.remainingQuota}>
<span style={styles.quotaText}>{selectedQuota}</span>
<span style={styles.timesText}>(times)</span>
</div>
</div>
</div>
<div className="form-group row align-items-center">
{/* Kolom untuk input NIK */}
<div className="col-md-6">
<div className="input-wrapper">
<input
type="text"
id="nik"
className="form-control"
value={nik}
onChange={handleNikChange} // Fungsi untuk menangani perubahan input NIK
placeholder="Enter NIK"
/>
</div>
</div>
{/* Kolom untuk Select dengan User Content */}
<div className="col-md-6">
<Select
id="userContent"
value={userContentOptions.find(option => option.value === userContentId)}
onChange={handleUserContentChange} // Fungsi untuk menangani perubahan pilihan user content
options={userContentOptions}
placeholder="Select User Content"
isSearchable
menuPortalTarget={document.body}
menuPlacement="auto"
inputValue={inputValueUserContent}
onInputChange={handleInputChangeUserContent}
/>
</div>
</div>
<div className="form-group row align-items-center mt-4">
<div className="col-md-6">
<textarea
id="messageId"
className="form-control"
style={styles.textarea}
value={messageId}
onChange={(e) => setMessageId(e.target.value)}
placeholder="Message Info"
/>
<div style={styles.characterCount}>
<p style={styles.text}>Character: 0 (Max. 459), SMS Count: 0</p>
</div>
</div>
</div>
<div style={styles.submitButton}>
<button onClick={handleClick} className="btn d-flex justify-content-center align-items-center me-2" style={styles.submitBtn}>
<p className="text-white mb-0">Check Now</p>
</button>
</div>
</div>
);
};
export default Nik;

View File

@ -0,0 +1,254 @@
import React, { useState, useEffect } from 'react';
import Select from 'react-select';
const BASE_URL = process.env.REACT_APP_BASE_URL;
const API_KEY = process.env.REACT_APP_API_KEY;
const Npwp = () => {
const [isLoading, setIsLoading] = useState(false);
const [applicationId, setApplicationId] = useState('');
const [messageId, setMessageId] = useState('');
const [inputValueApplication, setInputValueApplication] = useState('');
const [applicationIds, setApplicationIds] = useState([]);
const [isMobile, setIsMobile] = useState(false);
const [npwp, setNpwp] = useState('');
const [selectedQuota, setSelectedQuota] = useState(0);
// Fungsi untuk menangani perubahan pada NPWP
const handleNpwpChange = (e) => {
setNpwp(e.target.value);
};
const applicationOptions = applicationIds.map(app => ({
value: app.id,
label: app.name
}));
const handleApplicationChange = (selectedOption) => {
if (selectedOption) {
const selectedId = selectedOption.value;
const selectedApp = applicationIds.find(app => app.id === parseInt(selectedId));
if (selectedApp) {
setApplicationId(selectedId);
setSelectedQuota(selectedApp.quota); // Set the selected quota
}
}
};
const handleInputChangeApplication = (newInputValue) => {
if (newInputValue.length <= 15) {
setInputValueApplication(newInputValue);
}
};
const fetchApplicationIds = async () => {
try {
const response = await fetch(`${BASE_URL}/application/list`, {
method: 'GET',
headers: {
'accept': 'application/json',
'x-api-key': API_KEY,
},
});
const data = await response.json();
return data.details.data;
} catch (error) {
console.error('Error fetching application IDs:', error);
return [];
}
};
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
const data = await fetchApplicationIds();
setApplicationIds(data);
setIsLoading(false);
};
fetchData();
const checkIfMobile = () => {
setIsMobile(window.innerWidth <= 768);
};
checkIfMobile();
window.addEventListener('resize', checkIfMobile);
return () => window.removeEventListener('resize', checkIfMobile);
}, []);
const handleClick = () => {
setSelectedQuota(prevQuota => prevQuota - 1); // Mengurangi nilai selectedQuota
console.log('Click Check Now!'); // Tampilkan log pada klik
};
const styles = {
text: {
fontSize: '16px',
fontWeight: '400',
margin: '0',
marginTop: '8px',
},
remainingQuotaWrapper: {
marginTop: '2vh',
},
remainingQuota: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
marginTop: '4px',
},
quotaText: {
fontSize: '40px',
color: '#0542cc',
fontWeight: '600',
},
timesText: {
marginLeft: '8px',
verticalAlign: 'super',
fontSize: '20px',
},
submitButton: {
marginLeft: 'auto',
marginTop: '4rem',
textAlign: 'start',
position: 'relative',
zIndex: 1,
},
submitBtn: {
backgroundColor: '#0542CC',
},
loadingOverlay: {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.2)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: 1000,
},
spinner: {
border: '4px solid rgba(0, 0, 0, 0.1)',
borderLeftColor: '#0542cc',
borderRadius: '50%',
width: '90px',
height: '90px',
animation: 'spin 1s ease-in-out infinite',
},
select: {
width: '100%',
paddingRight: '30px',
fontSize: '16px',
border: '1px solid #ccc',
outline: 'none',
},
OtpId: {
marginTop: isMobile ? '2vh' : '0',
},
PhoneNumber: {
marginTop: isMobile ? '2vh' : '-12vh',
},
textarea: {
width: '100%',
height: '200px',
resize: 'none',
border: '1px solid #ccc',
outline: 'none',
},
characterCount: {
display: 'flex',
justifyContent: 'flex-end',
marginTop: '8px',
},
};
return (
<div>
<style>
{`
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`}
</style>
{isLoading && (
<div style={styles.loadingOverlay}>
<div style={styles.spinner}></div>
</div>
)}
<div className="form-group row align-items-center">
<div className="col-md-6">
<Select
id="applicationId"
value={applicationOptions.find(option => option.value === applicationId)}
onChange={handleApplicationChange}
options={applicationOptions}
placeholder="Select Application ID"
isSearchable
menuPortalTarget={document.body}
menuPlacement="auto"
inputValue={inputValueApplication}
onInputChange={handleInputChangeApplication}
/>
</div>
<div className="col-md-6" style={styles.remainingQuotaWrapper}>
<p style={styles.text}>Remaining Quota</p>
<div style={styles.remainingQuota}>
<span style={styles.quotaText}>{selectedQuota}</span>
<span style={styles.timesText}>(times)</span>
</div>
</div>
</div>
<div className="form-group row align-items-center">
{/* Kolom untuk input Npwp */}
<div className="col-md-6">
<div className="input-wrapper">
<input
type="text"
id="npwp"
className="form-control"
value={npwp}
onChange={handleNpwpChange} // Fungsi untuk menangani perubahan input NIK
placeholder="Enter NPWP"
/>
</div>
</div>
</div>
<div className="form-group row align-items-center mt-4">
<div className="col-md-6">
<textarea
id="messageId"
className="form-control"
style={styles.textarea}
value={messageId}
onChange={(e) => setMessageId(e.target.value)}
placeholder="Message Info"
/>
<div style={styles.characterCount}>
<p style={styles.text}>Character: 0 (Max. 459), SMS Count: 0</p>
</div>
</div>
</div>
<div style={styles.submitButton}>
<button onClick={handleClick} className="btn d-flex justify-content-center align-items-center me-2" style={styles.submitBtn}>
<p className="text-white mb-0">Check Now</p>
</button>
</div>
</div>
);
};
export default Npwp;

View File

@ -0,0 +1,7 @@
import Nik from './Nik';
import Npwp from './Npwp';
export {
Nik,
Npwp
}

View File

@ -0,0 +1,411 @@
import React, { useState, useEffect } from 'react';
import { FaChevronLeft, FaChevronRight, FaFastBackward, FaFastForward, FaSort, FaSortUp, FaSortDown } from 'react-icons/fa'; // Icons for sorting
import { NoAvailable } from '../../../assets/icon';
// Pagination Component
const Pagination = ({ currentPage, totalPages, onPageChange }) => {
const handlePrev = () => {
if (currentPage > 1) {
onPageChange(currentPage - 1);
}
};
const handleNext = () => {
if (currentPage < totalPages) {
onPageChange(currentPage + 1);
}
};
const handleFirst = () => {
onPageChange(1); // Go to first page
};
const handleLast = () => {
onPageChange(totalPages); // Go to last page
};
// Logic to display only 3 pages in pagination
const getPaginationRange = () => {
const range = [];
const totalPagesCount = totalPages;
let start = currentPage - 1;
let end = currentPage + 1;
// Adjust start and end if near the boundaries
if (currentPage === 1) {
start = 1;
end = Math.min(3, totalPagesCount);
} else if (currentPage === totalPages) {
start = Math.max(totalPagesCount - 2, 1);
end = totalPagesCount;
}
for (let i = start; i <= end; i++) {
range.push(i);
}
return range;
};
const pageRange = getPaginationRange();
return (
<div className="pagination-container d-flex justify-content-end mt-4">
{/* First Page Button */}
<button
className="btn"
onClick={handleFirst}
disabled={currentPage === 1}
>
<FaFastBackward /> {/* Double Arrow Left */}
</button>
<button
className="btn"
onClick={handlePrev}
disabled={currentPage === 1}
>
<FaChevronLeft /> {/* Single Arrow Left */}
</button>
{/* Page Numbers */}
{pageRange.map((pageNum) => (
<button
key={pageNum}
className={`btn ${pageNum === currentPage ? 'btn-primary' : ''}`}
onClick={() => onPageChange(pageNum)}
>
{pageNum}
</button>
))}
<button
className="btn"
onClick={handleNext}
disabled={currentPage === totalPages}
>
<FaChevronRight /> {/* Single Arrow Right */}
</button>
{/* Last Page Button */}
<button
className="btn"
onClick={handleLast}
disabled={currentPage === totalPages}
>
<FaFastForward /> {/* Double Arrow Right */}
</button>
</div>
);
};
const Transaction = () => {
const [currentPage, setCurrentPage] = useState(1);
const [isMobile, setIsMobile] = useState(false); // State to detect mobile view
const [transactionData, setTransactionData] = useState([]);
const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc' }); // Sorting state
const dataPerPage = 10; // Data per page (10 data per page)
const buttonData = [
{ label: 'Copy', enabled: true },
{ label: 'CSV', enabled: true },
{ label: 'Excel', enabled: true },
{ label: 'PDF', enabled: true },
{ label: 'Print', enabled: true },
{ label: 'Column Visibility', enabled: true },
];
// Generate 691 dummy transactions
const generateDummyData = (numOfItems) => {
const transactionData = [];
for (let i = 1; i <= numOfItems; i++) {
transactionData.push({
transactionId: `TX${String(i).padStart(3, '0')}`, // Transaction ID
applicationName: `App ${Math.floor(Math.random() * 5) + 1}`, // Application Name
referenceId: `REF${String(i).padStart(3, '0')}`, // Reference ID
dateSent: new Date(2023, Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1).toLocaleDateString(), // Date Sent (random date)
method: Math.random() > 0.5 ? 'Online' : 'Offline', // Method (randomly Online or Offline)
status: ['Completed', 'Pending', 'Failed'][Math.floor(Math.random() * 3)], // Status acak
});
}
return transactionData;
};
// Set the generated transaction data
useEffect(() => {
setTransactionData(generateDummyData(122)); // count data dummy transactions
}, []);
// Sorting function
const sortData = (data, config) => {
const { key, direction } = config;
return [...data].sort((a, b) => {
if (a[key] < b[key]) {
return direction === 'asc' ? -1 : 1;
}
if (a[key] > b[key]) {
return direction === 'asc' ? 1 : -1;
}
return 0;
});
};
// Handle column header sort click
const handleSort = (key) => {
let direction = 'asc';
if (sortConfig.key === key && sortConfig.direction === 'asc') {
direction = 'desc'; // Toggle direction if the same column is clicked
}
setSortConfig({ key, direction });
};
// Get the paginated data
const getPaginatedData = (data, page, perPage) => {
const sortedData = sortData(data, sortConfig);
const startIndex = (page - 1) * perPage;
const endIndex = startIndex + perPage;
return sortedData.slice(startIndex, endIndex);
};
// Handle page change
const handlePageChange = (page) => {
setCurrentPage(page);
};
// Calculate total pages based on the data and data per page
const totalPages = Math.ceil(transactionData.length / dataPerPage);
// Paginated data
const paginatedData = getPaginatedData(transactionData, currentPage, dataPerPage);
// Detect screen size and update isMobile state
useEffect(() => {
const handleResize = () => {
setIsMobile(window.innerWidth <= 768); // Change 768 to your breakpoint
};
handleResize();
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return (
<div className="container mt-5">
{/* Welcome Message */}
<div className="row-card border-left border-primary shadow mb-4" style={{ backgroundColor: '#E2FBEA' }}>
<div className="d-flex flex-column justify-content-start align-items-start p-4">
<div>
<h4 className="mb-3 text-start">
<i className="fas fa-warning fa-bold me-3"></i>Alert
</h4>
<p className="mb-0 text-start">
Get started now by creating an Application ID and explore all the demo services available on the dashboard.
Experience the ease and flexibility of trying out all our features firsthand.
</p>
</div>
</div>
</div>
<div style={styles.contentContainer}>
{/* Filter Form */}
<div className="card p-3 mb-4">
<div className="row">
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<label>Start Date</label>
<input type="date" className="form-control" />
</div>
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<label>End Date</label>
<input type="date" className="form-control" />
</div>
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<label>Application</label>
<select className="form-control">
<option>Select Application</option>
<option>App 1</option>
<option>App 2</option>
<option>App 3</option>
<option>App 4</option>
<option>App 5</option>
</select>
</div>
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<label>Status</label>
<select className="form-control">
<option>Select Status</option>
<option>Status 1</option>
<option>Status 2</option>
<option>Status 3</option>
<option>Status 4</option>
<option>Status 5</option>
</select>
</div>
<div className={`col-12 ${isMobile ? 'd-flex justify-content-between' : 'col-md-2 d-flex align-items-end'}`} style={{ gap: '10px' }}>
<button className="btn btn-primary w-48">Apply</button>
<button className="btn btn-secondary w-48">Cancel</button>
</div>
</div>
</div>
{/* Action Buttons */}
<div className="d-flex justify-content-between align-items-center mb-3">
<div>
{buttonData.map((button, index) =>
button.enabled ? (
<button
key={index}
className={`btn btn-light ${isMobile ? 'mb-2' : ''}`} // Add margin on mobile
style={styles.actionButton}
>
{button.label}
</button>
) : null
)}
</div>
{/* Search Bar with Icon */}
<div className="input-group" style={{ width: '250px', display: 'flex', alignItems: 'center', justifyContent: 'flex-end' }}>
<input
type="text"
placeholder="Search..."
className="form-control"
/>
<span className="input-group-text">
<i className="fas fa-search"></i> {/* FontAwesome search icon */}
</span>
</div>
</div>
{/* Table */}
<div className="table-responsive">
<table className="table table-bordered" style={styles.tableContainer}>
<thead>
<tr>
<th>No.</th> {/* Kolom untuk Nomor Urut */}
<th>
<button className="btn" onClick={() => handleSort('transactionId')}>
Transaction ID
{sortConfig.key === 'transactionId' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'transactionId' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('applicationName')}>
Application Name
{sortConfig.key === 'applicationName' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'applicationName' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('referenceId')}>
Reference ID
{sortConfig.key === 'referenceId' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'referenceId' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('dateSent')}>
Date Sent
{sortConfig.key === 'dateSent' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'dateSent' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('method')}>
Method
{sortConfig.key === 'method' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'method' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('status')}>
Status
{sortConfig.key === 'status' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'status' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
</tr>
</thead>
<tbody>
{paginatedData.length > 0 ? (
paginatedData.map((transaction, index) => (
<tr key={index}>
{/* Kolom Nomor Urut */}
<td>{(currentPage - 1) * dataPerPage + index + 1}</td> {/* Nomor urut berdasarkan halaman dan index */}
<td>{transaction.transactionId}</td>
<td>{transaction.applicationName}</td>
<td>{transaction.referenceId}</td>
<td>{transaction.dateSent}</td>
<td>{transaction.method}</td>
<td>{transaction.status}</td>
</tr>
))
) : (
<tr>
<td colSpan="7" className="text-center">
<div className="d-flex flex-column align-items-center mt-5">
<img src={NoAvailable} alt="No Data Available" className="mb-3" style={styles.iconStyle} />
<p>Data not available</p>
</div>
</td>
</tr>
)}
</tbody>
</table>
</div>
{/* Pagination */}
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={handlePageChange}
/>
</div>
</div>
);
};
export default Transaction;
const styles = {
contentContainer: {
padding: '20px',
border: '0.1px solid rgba(0, 0, 0, 0.2)',
borderLeft: '4px solid #0542cc',
borderRadius: '10px',
width: '100%',
},
tableContainer: {
minHeight: '300px',
maxHeight: '1500px',
overflowY: 'auto',
},
iconStyle: {
width: '50px',
height: '50px',
},
// Add margin-left style for icons
iconMarginLeft: {
marginLeft: '0.7rem', // Adjust as needed
},
};

View File

@ -0,0 +1,114 @@
import React, { useEffect, useState } from 'react';
import { Link, Routes, Route, useNavigate } from 'react-router-dom';
import {
Nik,
Npwp
} from './Section';
const Verify = () => {
const verifyTabs = [
{ name: 'NPWP', link: 'identity-tax-npwp' },
{ name: 'NIK', link: 'identity-tax-nik' }
];
const [isMobile, setIsMobile] = useState(false);
const navigate = useNavigate();
// Redirect otomatis ke rute default saat akses ke /wa-verify
useEffect(() => {
if (window.location.pathname === '/identity-tax-verify') {
navigate('identity-tax-npwp', { replace: true });
}
}, [navigate]);
// Update state isMobile berdasarkan ukuran layar
useEffect(() => {
const handleResize = () => {
setIsMobile(window.innerWidth <= 768);
};
handleResize();
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return (
<div className="container" style={styles.container}>
{/* Static Content */}
<div className="row-card border-left border-primary shadow mb-4" style={styles.welcomeCard}>
<div className="d-flex flex-column justify-content-start align-items-start p-4">
<h4 className="mb-3 text-start">
<i className="fas fa-warning fa-bold me-3"></i>Alert
</h4>
<p className="mb-0 text-start">
Get started now by creating an Application ID and explore all the demo services available on the dashboard.
Experience the ease and flexibility of trying out all our features firsthand.
</p>
<div className="d-flex flex-row mt-3">
<Link to="/createApps" style={{ textDecoration: 'none' }}>
<button className="btn d-flex justify-content-center align-items-center me-2" style={styles.createButton}>
<i className="fas fa-plus text-white me-2"></i>
<p className="text-white mb-0">Create New App ID</p>
</button>
</Link>
</div>
</div>
</div>
{/* Tab Navigation */}
<div style={styles.section}>
<div className={`d-flex ${isMobile ? 'flex-column' : 'flex-row'} justify-content-between align-items-center mb-3`}>
<div className="d-flex flex-wrap">
{verifyTabs.map((tab) => (
<Link
key={tab.link}
to={tab.link}
className={`btn ${window.location.pathname.includes(tab.link) ? 'btn-primary' : 'btn-light'} me-2 mb-2`}
style={styles.tabLink}
>
{tab.name}
</Link>
))}
</div>
</div>
{/* Dynamic Tab Content */}
<div className="tab-content">
<Routes>
<Route path="identity-tax-npwp" element={<Npwp />} />
<Route path="identity-tax-nik" element={<Nik />} />
</Routes>
</div>
</div>
</div>
);
};
export default Verify;
const styles = {
container: {
marginTop: '3%',
padding: '0 15px',
},
welcomeCard: {
backgroundColor: '#E2FBEA',
borderLeft: '4px solid #0542CC',
borderRadius: '5px',
marginBottom: '20px',
},
createButton: {
backgroundColor: '#0542CC',
},
section: {
padding: '20px',
border: '0.1px solid rgba(0, 0, 0, 0.2)',
borderLeft: '4px solid #0542CC',
borderRadius: '10px',
width: '100%',
},
tabLink: {
padding: '10px 20px',
},
};

View File

@ -0,0 +1,7 @@
import Verify from './Verify';
import Transaction from './Transaction';
export {
Verify,
Transaction
}

View File

@ -0,0 +1,278 @@
import React, { useState } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
const Account = () => {
const [activeTab, setActiveTab] = useState('profile');
const [firstName, setFirstName] = useState("Murtadi");
const [lastName, setLastName] = useState("Mujaidi");
const [email, setEmail] = useState("Mujaidi@gmail.com");
// State untuk password
const [passwords, setPasswords] = useState({
currentPassword: '',
newPassword: '',
confirmNewPassword: ''
});
// State untuk visibilitas password
const [passwordVisible, setPasswordVisible] = useState(false);
const [newPasswordVisible, setNewPasswordVisible] = useState(false);
const [confirmNewPasswordVisible, setConfirmNewPasswordVisible] = useState(false);
// State untuk pesan kesalahan
const [errorMessages, setErrorMessages] = useState({
currentPasswordError: '',
newPasswordError: '',
confirmNewPasswordError: ''
});
// Fungsi untuk toggle visibility password
const togglePasswordVisibility = (e, type) => {
e.preventDefault();
if (type === 'current') {
setPasswordVisible(prevState => !prevState);
} else if (type === 'new') {
setNewPasswordVisible(prevState => !prevState);
} else if (type === 'confirm') {
setConfirmNewPasswordVisible(prevState => !prevState);
}
};
// Fungsi untuk menangani perubahan password
const handlePasswordChange = (e) => {
const { name, value } = e.target;
setPasswords(prevState => ({
...prevState,
[name]: value
}));
};
// Validasi password
const validatePasswords = () => {
const { currentPassword, newPassword, confirmNewPassword } = passwords;
let isValid = true;
let errors = {
currentPasswordError: '',
newPasswordError: '',
confirmNewPasswordError: ''
};
if (!currentPassword || !newPassword || !confirmNewPassword) {
errors.currentPasswordError = 'Please fill in all password fields.';
isValid = false;
}
if (newPassword !== confirmNewPassword) {
errors.newPasswordError = 'New password and confirm password do not match.';
isValid = false;
}
if (newPassword === currentPassword) {
errors.newPasswordError = 'New password cannot be the same as the current password.';
isValid = false;
}
setErrorMessages(errors);
return isValid;
};
// Styles untuk elemen-elemen
const styles = {
tabContainer: {
border: '1px solid #d3d3d3',
borderLeft: '4px solid #0542cc',
alignItems: 'center',
justifyContent: 'flex-start',
display: 'flex',
boxShadow: '0 4px 8px rgba(0, 0, 0, 0.1)',
},
tabButton: {
display: 'flex',
justifyContent: 'flex-start',
alignItems: 'center',
},
contentContainer: {
border: '1px solid #d3d3d3',
height: '23rem',
overflowY: 'auto',
padding: '1rem 0 0 1.7rem',
boxShadow: '0 4px 8px rgba(0, 0, 0, 0.1)',
},
inputGroup: {
width: '200px',
},
errorText: {
color: 'red',
fontSize: '0.8rem',
}
};
const renderTabContent = () => {
switch (activeTab) {
case 'profile':
return (
<div>
<h3>My Profile</h3>
<form className='mt-3'>
<div className='col-6'>
<div className="input-group mb-3">
<span className="input-group-text" style={styles.inputGroup}>First Name</span>
<input
type="text"
className="form-control"
id="firstName"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
/>
</div>
</div>
<div className='col-6'>
<div className="input-group mb-3">
<span className="input-group-text" style={styles.inputGroup}>Last Name</span>
<input
type="text"
className="form-control"
id="lastName"
value={lastName}
onChange={(e) => setLastName(e.target.value)}
/>
</div>
</div>
<div className='col-6'>
<div className="input-group mb-3">
<span className="input-group-text" style={styles.inputGroup}>Email</span>
<input
type="email"
className="form-control"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
</div>
<button type="button" className="btn btn-primary">
Edit Profile
</button>
</form>
</div>
);
case 'password':
return (
<>
<h3>Update Password</h3>
<form onSubmit={(e) => { e.preventDefault(); if (validatePasswords()) { alert('Password updated successfully!'); } }}>
<div className='col-6 mb-2'>
<div className="input-group mb-3">
<span className="input-group-text" style={styles.inputGroup}>Current Password</span>
<input
type={passwordVisible ? 'text' : 'password'}
className="form-control"
name="currentPassword"
value={passwords.currentPassword}
onChange={handlePasswordChange}
/>
<button
className="input-group-text"
onClick={(e) => togglePasswordVisibility(e, 'current')}
style={{ cursor: 'pointer' }}
>
<FontAwesomeIcon icon={passwordVisible ? "fa-solid fa-eye" : "fa-solid fa-eye-slash"} />
</button>
</div>
{errorMessages.currentPasswordError && (
<small className="text-danger" style={styles.errorText}>{errorMessages.currentPasswordError}</small>
)}
</div>
<div className='col-6 mb-2'>
<div className="input-group mb-3">
<span className="input-group-text" style={styles.inputGroup}>New Password</span>
<input
type={newPasswordVisible ? 'text' : 'password'}
className="form-control"
name="newPassword"
value={passwords.newPassword}
onChange={handlePasswordChange}
/>
<button
className="input-group-text"
onClick={(e) => togglePasswordVisibility(e, 'new')}
style={{ cursor: 'pointer' }}
>
<FontAwesomeIcon icon={newPasswordVisible ? "fa-solid fa-eye" : "fa-solid fa-eye-slash"} />
</button>
</div>
{errorMessages.newPasswordError && (
<small className="text-danger" style={styles.errorText}>{errorMessages.newPasswordError}</small>
)}
</div>
<div className='col-6 mb-2'>
<div className="input-group mb-3">
<span className="input-group-text" style={styles.inputGroup}>Confirm New Password</span>
<input
type={confirmNewPasswordVisible ? 'text' : 'password'}
className="form-control"
name="confirmNewPassword"
value={passwords.confirmNewPassword}
onChange={handlePasswordChange}
/>
<button
className="input-group-text"
onClick={(e) => togglePasswordVisibility(e, 'confirm')}
style={{ cursor: 'pointer' }}
>
<FontAwesomeIcon icon={confirmNewPasswordVisible ? "fa-solid fa-eye" : "fa-solid fa-eye-slash"} />
</button>
</div>
{errorMessages.confirmNewPasswordError && (
<small className="text-danger" style={styles.errorText}>{errorMessages.confirmNewPasswordError}</small>
)}
</div>
<button type="submit" className="btn btn-primary">
Save
</button>
</form>
</>
);
default:
return null;
}
};
return (
<div className="container mt-5">
<div className="row gx-5">
{/* Tab Navigation - 3 Col */}
<div className="col-md-3 border-end" style={styles.tabContainer}>
<div className="nav flex-column nav-pills">
<button
className={`nav-link ${activeTab === 'profile' ? 'active' : ''}`}
onClick={() => setActiveTab('profile')}
style={styles.tabButton}
>
<i className="fas fa-user-circle me-2"></i> My Profile
</button>
<button
className={`nav-link ${activeTab === 'password' ? 'active' : ''}`}
onClick={() => setActiveTab('password')}
style={styles.tabButton}
>
<i className="fas fa-key me-2"></i> Update Password
</button>
</div>
</div>
{/* Content Area - 9 Col */}
<div className="col-md-9">
<div style={styles.contentContainer}>
{renderTabContent()}
</div>
</div>
</div>
</div>
);
};
export default Account;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,11 @@
import React from 'react'
const Production = () => {
return (
<div>
<h1>Production</h1>
</div>
)
}
export default Production

View File

@ -0,0 +1,11 @@
import React from 'react'
const Trial = () => {
return (
<div>
<h1>Trial</h1>
</div>
)
}
export default Trial

View File

@ -0,0 +1,11 @@
import Account from './Account';
import Company from './Company';
import Production from './Production';
import Trial from './Trial';
export {
Account,
Company,
Production,
Trial
}

View File

@ -0,0 +1,200 @@
import React, { useState, useEffect } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faShieldAlt } from '@fortawesome/free-solid-svg-icons';
import Select from 'react-select';
const BASE_URL = process.env.REACT_APP_BASE_URL;
const API_KEY = process.env.REACT_APP_API_KEY;
const Admin = () => {
const [isLoading, setIsLoading] = useState(false);
const [applicationId, setApplicationId] = useState('');
const [inputValueApplication, setInputValueApplication] = useState('');
const [applicationIds, setApplicationIds] = useState([]);
const [isMobile, setIsMobile] = useState(false);
const applicationOptions = applicationIds.map(app => ({
value: app.id,
label: app.name
}));
const handleApplicationChange = (selectedOption) => {
if (selectedOption) {
const selectedId = selectedOption.value;
const selectedApp = applicationIds.find(app => app.id === parseInt(selectedId));
if (selectedApp) {
setApplicationId(selectedId);
}
}
};
const handleInputChangeApplication = (newInputValue) => {
if (newInputValue.length <= 15) {
setInputValueApplication(newInputValue);
}
};
const fetchApplicationIds = async () => {
try {
const response = await fetch(`${BASE_URL}/application/list`, {
method: 'GET',
headers: {
'accept': 'application/json',
'x-api-key': API_KEY,
},
});
const data = await response.json();
return data.details.data;
} catch (error) {
console.error('Error fetching application IDs:', error);
return [];
}
};
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
const data = await fetchApplicationIds();
setApplicationIds(data);
setIsLoading(false);
};
fetchData();
const checkIfMobile = () => {
setIsMobile(window.innerWidth <= 768);
};
checkIfMobile();
window.addEventListener('resize', checkIfMobile);
return () => window.removeEventListener('resize', checkIfMobile);
}, []);
const styles = {
container: {
padding: '1rem',
width: '100%',
margin: '0 auto',
boxSizing: 'border-box',
},
wrapper: {
display: 'flex',
flexDirection: 'column',
borderRadius: '8px',
padding: '1.5rem',
maxWidth: '1200px',
margin: '0 auto',
},
wrapperMobile: {
display: 'flex',
flexDirection: 'column',
borderRadius: '8px',
padding: '1rem',
},
welcomeCard: {
backgroundColor: '#E2FBEA',
borderLeft: '4px solid #0542CC',
borderRadius: '5px',
marginBottom: '20px',
},
section: {
padding: '20px',
border: '0.1px solid rgba(0, 0, 0, 0.2)',
borderLeft: '4px solid #0542CC',
borderRadius: '10px',
width: '100%',
},
loadingOverlay: {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.2)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: 1000,
},
spinner: {
border: '4px solid rgba(0, 0, 0, 0.1)',
borderLeftColor: '#0542cc',
borderRadius: '50%',
width: '90px',
height: '90px',
animation: 'spin 1s ease-in-out infinite',
},
select: {
width: '100%',
paddingRight: '30px',
fontSize: '16px',
border: '1px solid #ccc',
outline: 'none',
}
};
return (
<>
<style>
{`
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`}
</style>
{isLoading && (
<div style={styles.loadingOverlay}>
<div style={styles.spinner}></div>
</div>
)}
<div style={styles.container}>
<div style={isMobile ? styles.wrapperMobile : styles.wrapper}>
{/* Static Content */}
<div className="row-card border-left border-primary shadow mb-4" style={styles.welcomeCard}>
<div className="d-flex flex-column justify-content-start align-items-start p-4">
<h4 className="mb-3 text-start">
{/* Menggunakan FontAwesomeIcon */}
<FontAwesomeIcon icon={faShieldAlt} style={{ fontWeight: 'bold', marginRight: '10px' }} />
Stay Safe & Secure
</h4>
<p className="mb-0 text-start">
Get started now by creating an Application ID and explore all the demo services available on the dashboard.
Experience the ease and flexibility of trying out all our features firsthand.
</p>
</div>
</div>
<div style={styles.section}>
<div className="form-group row align-items-center">
<h1>Admin Settings</h1>
<p style={{color: '#1F2D3D'}}>Manage your security by setting or updating your Application ID PIN for enhanced protection and easy access control.</p>
<div className="col-md-8 mt-3">
<Select
id="applicationId"
value={applicationOptions.find(option => option.value === applicationId)}
onChange={handleApplicationChange}
options={applicationOptions}
placeholder="Select Application ID"
isSearchable
menuPortalTarget={document.body}
menuPlacement="auto"
inputValue={inputValueApplication}
onInputChange={handleInputChangeApplication}
/>
</div>
</div>
</div>
</div>
</div>
</>
);
};
export default Admin;

View File

@ -0,0 +1,398 @@
import React, { useState, useEffect } from 'react';
import { FaChevronLeft, FaChevronRight, FaFastBackward, FaFastForward, FaSort, FaSortUp, FaSortDown } from 'react-icons/fa'; // Icons for sorting
import { NoAvailable } from '../../../assets/icon';
// Pagination Component
const Pagination = ({ currentPage, totalPages, onPageChange }) => {
const handlePrev = () => {
if (currentPage > 1) {
onPageChange(currentPage - 1);
}
};
const handleNext = () => {
if (currentPage < totalPages) {
onPageChange(currentPage + 1);
}
};
const handleFirst = () => {
onPageChange(1); // Go to first page
};
const handleLast = () => {
onPageChange(totalPages); // Go to last page
};
// Logic to display only 3 pages in pagination
const getPaginationRange = () => {
const range = [];
const totalPagesCount = totalPages;
let start = currentPage - 1;
let end = currentPage + 1;
// Adjust start and end if near the boundaries
if (currentPage === 1) {
start = 1;
end = Math.min(3, totalPagesCount);
} else if (currentPage === totalPages) {
start = Math.max(totalPagesCount - 2, 1);
end = totalPagesCount;
}
for (let i = start; i <= end; i++) {
range.push(i);
}
return range;
};
const pageRange = getPaginationRange();
return (
<div className="pagination-container d-flex justify-content-end mt-4">
{/* First Page Button */}
<button
className="btn"
onClick={handleFirst}
disabled={currentPage === 1}
>
<FaFastBackward /> {/* Double Arrow Left */}
</button>
<button
className="btn"
onClick={handlePrev}
disabled={currentPage === 1}
>
<FaChevronLeft /> {/* Single Arrow Left */}
</button>
{/* Page Numbers */}
{pageRange.map((pageNum) => (
<button
key={pageNum}
className={`btn ${pageNum === currentPage ? 'btn-primary' : ''}`}
onClick={() => onPageChange(pageNum)}
>
{pageNum}
</button>
))}
<button
className="btn"
onClick={handleNext}
disabled={currentPage === totalPages}
>
<FaChevronRight /> {/* Single Arrow Right */}
</button>
{/* Last Page Button */}
<button
className="btn"
onClick={handleLast}
disabled={currentPage === totalPages}
>
<FaFastForward /> {/* Double Arrow Right */}
</button>
</div>
);
};
const Monitor = () => {
const [currentPage, setCurrentPage] = useState(1);
const [isMobile, setIsMobile] = useState(false); // State to detect mobile view
const [transactionData, setTransactionData] = useState([]);
const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc' }); // Sorting state
const dataPerPage = 10; // Data per page (10 data per page)
const buttonData = [
{ label: 'Copy', enabled: true },
{ label: 'CSV', enabled: true },
{ label: 'Excel', enabled: true },
{ label: 'PDF', enabled: true },
{ label: 'Print', enabled: true },
{ label: 'Column Visibility', enabled: true },
];
// Generate 691 dummy transactions
const generateDummyData = (numOfItems) => {
const transactionData = [];
for (let i = 1; i <= numOfItems; i++) {
transactionData.push({
transactionId: `TX${String(i).padStart(3, '0')}`, // Transaction ID
referenceId: `REF${String(i).padStart(3, '0')}`, // Reference ID
name: `Name ${Math.floor(Math.random() * 1000) + 1}`, // Random Name
createdDate: new Date(2020 + Math.floor(Math.random() * 5), Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1).toLocaleDateString(), // Created Date (random date between 2020-2024)
updatedDate: new Date(2020 + Math.floor(Math.random() * 5), Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1).toLocaleDateString(), // Updated Date (random date, similar to createdDate)
update: `Update ${Math.floor(Math.random() * 100) + 1}`, // Random update message
});
}
return transactionData;
};
// Set the generated transaction data
useEffect(() => {
setTransactionData(generateDummyData(122)); // count data dummy transactions
}, []);
// Sorting function
const sortData = (data, config) => {
const { key, direction } = config;
return [...data].sort((a, b) => {
if (a[key] < b[key]) {
return direction === 'asc' ? -1 : 1;
}
if (a[key] > b[key]) {
return direction === 'asc' ? 1 : -1;
}
return 0;
});
};
// Handle column header sort click
const handleSort = (key) => {
let direction = 'asc';
if (sortConfig.key === key && sortConfig.direction === 'asc') {
direction = 'desc'; // Toggle direction if the same column is clicked
}
setSortConfig({ key, direction });
};
// Get the paginated data
const getPaginatedData = (data, page, perPage) => {
const sortedData = sortData(data, sortConfig);
const startIndex = (page - 1) * perPage;
const endIndex = startIndex + perPage;
return sortedData.slice(startIndex, endIndex);
};
// Handle page change
const handlePageChange = (page) => {
setCurrentPage(page);
};
// Calculate total pages based on the data and data per page
const totalPages = Math.ceil(transactionData.length / dataPerPage);
// Paginated data
const paginatedData = getPaginatedData(transactionData, currentPage, dataPerPage);
// Detect screen size and update isMobile state
useEffect(() => {
const handleResize = () => {
setIsMobile(window.innerWidth <= 768); // Change 768 to your breakpoint
};
handleResize();
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return (
<div className="container mt-5">
{/* Welcome Message */}
<div className="row-card border-left border-primary shadow mb-4" style={{ backgroundColor: '#E2FBEA' }}>
<div className="d-flex flex-column justify-content-start align-items-start p-4">
<div>
<h4 className="mb-3 text-start">
<i className="fas fa-warning fa-bold me-3"></i>Alert
</h4>
<p className="mb-0 text-start">
Get started now by creating an Application ID and explore all the demo services available on the dashboard.
Experience the ease and flexibility of trying out all our features firsthand.
</p>
</div>
</div>
</div>
<div style={styles.contentContainer}>
{/* Filter Form */}
<div className="card p-3 mb-4">
<div className="row">
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<input type="date" className="form-control" />
</div>
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<input type="date" className="form-control" />
</div>
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<select className="form-control">
<option>Sort</option>
<option>App 1</option>
<option>App 2</option>
<option>App 3</option>
<option>App 4</option>
<option>App 5</option>
</select>
</div>
<div className={`col-12 ${isMobile ? 'd-flex justify-content-between' : 'col-md-2 d-flex align-items-end'}`} style={{ gap: '10px' }}>
<button className="btn btn-primary w-48">Apply</button>
<button className="btn btn-secondary w-48">Cancel</button>
</div>
</div>
</div>
{/* Action Buttons */}
<div className="d-flex justify-content-between align-items-center mb-3">
<div>
{buttonData.map((button, index) =>
button.enabled ? (
<button
key={index}
className={`btn btn-light ${isMobile ? 'mb-2' : ''}`} // Add margin on mobile
style={styles.actionButton}
>
{button.label}
</button>
) : null
)}
</div>
{/* Search Bar with Icon */}
<div className="input-group" style={{ width: '250px', display: 'flex', alignItems: 'center', justifyContent: 'flex-end' }}>
<input
type="text"
placeholder="Search..."
className="form-control"
/>
<span className="input-group-text">
<i className="fas fa-search"></i> {/* FontAwesome search icon */}
</span>
</div>
</div>
{/* Table */}
<div className="table-responsive">
<table className="table table-bordered" style={styles.tableContainer}>
<thead>
<tr>
<th>No.</th> {/* Kolom untuk Nomor Urut */}
<th>
<button className="btn" onClick={() => handleSort('transactionId')}>
Transaction ID
{sortConfig.key === 'transactionId' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'transactionId' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('referenceId')}>
Reference ID
{sortConfig.key === 'referenceId' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'referenceId' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('name')}>
Name
{sortConfig.key === 'name' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'name' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('createdDate')}>
Created Date
{sortConfig.key === 'createdDate' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'createdDate' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('updatedDate')}>
Updated Date
{sortConfig.key === 'updatedDate' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'updatedDate' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('update')}>
Update
{sortConfig.key === 'update' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'update' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
</tr>
</thead>
<tbody>
{paginatedData.length > 0 ? (
paginatedData.map((transaction, index) => (
<tr key={index}>
{/* Kolom Nomor Urut */}
<td>{(currentPage - 1) * dataPerPage + index + 1}</td> {/* Nomor urut berdasarkan halaman dan index */}
<td>{transaction.transactionId}</td>
<td>{transaction.referenceId}</td>
<td>{transaction.name}</td>
<td>{transaction.createdDate}</td>
<td>{transaction.updatedDate}</td>
<td>{transaction.update}</td>
</tr>
))
) : (
<tr>
<td colSpan="7" className="text-center">
<div className="d-flex flex-column align-items-center mt-5">
<img src={NoAvailable} alt="No Data Available" className="mb-3" style={styles.iconStyle} />
<p>Data not available</p>
</div>
</td>
</tr>
)}
</tbody>
</table>
</div>
{/* Pagination */}
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={handlePageChange}
/>
</div>
</div>
);
};
export default Monitor;
const styles = {
contentContainer: {
padding: '20px',
border: '0.1px solid rgba(0, 0, 0, 0.2)',
borderLeft: '4px solid #0542cc',
borderRadius: '10px',
width: '100%',
},
tableContainer: {
minHeight: '300px',
maxHeight: '1500px',
overflowY: 'auto',
},
iconStyle: {
width: '50px',
height: '50px',
},
// Add margin-left style for icons
iconMarginLeft: {
verticalAlign: 'middle',
marginLeft: '0.5rem',
},
};

View File

@ -0,0 +1,420 @@
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import Select from 'react-select';
import { FaEye, FaEyeSlash } from 'react-icons/fa';
const BASE_URL = process.env.REACT_APP_BASE_URL;
const API_KEY = process.env.REACT_APP_API_KEY;
const Search = () => {
const [isLoading, setIsLoading] = useState(false);
const [applicationId, setApplicationId] = useState('');
const [inputValueApplication, setInputValueApplication] = useState('');
const [applicationIds, setApplicationIds] = useState([]);
const [fullName, setFullName] = useState('');
const [fullNameError, setFullNameError] = useState('');
const [date, setDate] = useState('');
const [dateError, setDateError] = useState('');
const [place, setPlace] = useState('');
const [placeError, setPlaceError] = useState('');
const [country, setCountry] = useState('');
const [countryError, setCountryError] = useState('');
const [referenceId, setReferenceId] = useState('');
const [referenceError, setReferenceError] = useState('');
const [yourId, setYourId] = useState('');
const [showInput, setShowInput] = useState(true);
const [isMobile, setIsMobile] = useState(false);
// Fungsi untuk menangani perubahan pada NPWP
const handleFullNameChange = (e) => {
setFullName(e.target.value);
};
const handleDateChange = (e) => {
setDate(e.target.value);
};
const handlePlaceChange = (e) => {
setPlace(e.target.value);
};
const handleCountryChange = (e) => {
setCountry(e.target.value);
};
const handleReferenceIDChange = (e) => {
setReferenceError(e.target.value);
};
const handleYourIdChange = (event) => {
setYourId(event.target.value);
};
const toggleInputVisibility = () => {
setShowInput(!showInput);
};
const applicationOptions = applicationIds.map(app => ({
value: app.id,
label: app.name
}));
const handleApplicationChange = (selectedOption) => {
if (selectedOption) {
const selectedId = selectedOption.value;
const selectedApp = applicationIds.find(app => app.id === parseInt(selectedId));
if (selectedApp) {
setApplicationId(selectedId);
}
}
};
const handleInputChangeApplication = (newInputValue) => {
if (newInputValue.length <= 15) {
setInputValueApplication(newInputValue);
}
};
const fetchApplicationIds = async () => {
try {
const response = await fetch(`${BASE_URL}/application/list`, {
method: 'GET',
headers: {
'accept': 'application/json',
'x-api-key': API_KEY,
},
});
const data = await response.json();
return data.details.data;
} catch (error) {
console.error('Error fetching application IDs:', error);
return [];
}
};
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
const data = await fetchApplicationIds();
setApplicationIds(data);
setIsLoading(false);
};
fetchData();
const checkIfMobile = () => {
setIsMobile(window.innerWidth <= 768);
};
checkIfMobile();
window.addEventListener('resize', checkIfMobile);
return () => window.removeEventListener('resize', checkIfMobile);
}, []);
const handleClick = () => {
console.log('Click Check Now!'); // Tampilkan log pada klik
};
const styles = {
container: {
padding: '1rem',
width: '100%',
margin: '0 auto',
boxSizing: 'border-box',
},
wrapper: {
display: 'flex',
flexDirection: 'column',
borderRadius: '8px',
padding: '1.5rem',
maxWidth: '1200px',
margin: '0 auto',
},
wrapperMobile: {
display: 'flex',
flexDirection: 'column',
borderRadius: '8px',
padding: '1rem',
},
breadcrumb: {
marginBottom: '1rem',
fontSize: '0.9rem',
},
breadcrumbLink: {
textDecoration: 'none',
color: '#0542cc',
},
tabLink: {
padding: '10px 20px',
},
welcomeCard: {
backgroundColor: '#E2FBEA',
borderLeft: '4px solid #0542CC',
borderRadius: '5px',
marginBottom: '20px',
},
createButton: {
backgroundColor: '#0542CC',
},
section: {
padding: '20px',
border: '0.1px solid rgba(0, 0, 0, 0.2)',
borderLeft: '4px solid #0542CC',
borderRadius: '10px',
width: '100%',
},
tabLink: {
padding: '10px 20px',
},
text: {
fontSize: '16px',
fontWeight: '400',
margin: '0',
marginTop: '8px',
},
remainingQuotaWrapper: {
marginTop: '2vh',
},
remainingQuota: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
marginTop: '4px',
},
quotaText: {
fontSize: '40px',
color: '#0542cc',
fontWeight: '600',
},
timesText: {
marginLeft: '8px',
verticalAlign: 'super',
fontSize: '20px',
},
submitButton: {
marginLeft: 'auto',
marginTop: '4rem',
textAlign: 'start',
position: 'relative',
zIndex: 1,
},
submitBtn: {
backgroundColor: '#0542CC',
},
loadingOverlay: {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.2)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: 1000,
},
spinner: {
border: '4px solid rgba(0, 0, 0, 0.1)',
borderLeftColor: '#0542cc',
borderRadius: '50%',
width: '90px',
height: '90px',
animation: 'spin 1s ease-in-out infinite',
},
select: {
width: '100%',
paddingRight: '30px',
fontSize: '16px',
border: '1px solid #ccc',
outline: 'none',
},
OtpId: {
marginTop: isMobile ? '2vh' : '0',
},
PhoneNumber: {
marginTop: isMobile ? '2vh' : '-12vh',
},
textarea: {
width: '100%',
height: '200px',
resize: 'none',
border: '1px solid #ccc',
outline: 'none',
},
characterCount: {
display: 'flex',
justifyContent: 'flex-end',
marginTop: '8px',
},
};
return (
<>
<style>
{`
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`}
</style>
{isLoading && (
<div style={styles.loadingOverlay}>
<div style={styles.spinner}></div>
</div>
)}
<div style={styles.container}>
<div style={isMobile ? styles.wrapperMobile : styles.wrapper}>
{/* Static Content */}
<div className="row-card border-left border-primary shadow mb-4" style={styles.welcomeCard}>
<div className="d-flex flex-column justify-content-start align-items-start p-4">
<h4 className="mb-3 text-start">
<i className="fas fa-warning fa-bold me-3"></i>Alert
</h4>
<p className="mb-0 text-start">
Get started now by creating an Application ID and explore all the demo services available on the dashboard.
Experience the ease and flexibility of trying out all our features firsthand.
</p>
<div className="d-flex flex-row mt-3">
<Link to="/createApps" style={{ textDecoration: 'none' }}>
<button className="btn d-flex justify-content-center align-items-center me-2" style={styles.createButton}>
<i className="fas fa-plus text-white me-2"></i>
<p className="text-white mb-0">Create New App ID</p>
</button>
</Link>
</div>
</div>
</div>
<div style={styles.section}>
<div className="form-group row align-items-center">
<div className="col-md-6">
<Select
id="applicationId"
value={applicationOptions.find(option => option.value === applicationId)}
onChange={handleApplicationChange}
options={applicationOptions}
placeholder="Select Application ID"
isSearchable
menuPortalTarget={document.body}
menuPlacement="auto"
inputValue={inputValueApplication}
onInputChange={handleInputChangeApplication}
/>
</div>
<div className="col-md-6">
<div className="input-wrapper">
<input
type="fullName"
id="fullName"
className="form-control"
value={fullName}
onChange={handleFullNameChange}
placeholder="Full Name"
/>
</div>
{/* Menampilkan error jika ada */}
{fullNameError && <small style={styles.SelfieImageError}>{fullNameError}</small>}
</div>
</div>
<div className="form-group row align-items-center mt-3">
<div className="col-md-6">
<div className="input-wrapper">
<label htmlFor="date" style={{ display: 'block', marginBottom: '5px' }}>
Date of Birth
</label>
<input
type="date"
id="date"
className="form-control"
value={date}
onChange={handleDateChange}
/>
</div>
{/* Menampilkan error jika ada */}
{dateError && <small style={styles.SelfieImageError}>{dateError}</small>}
</div>
<div className="col-md-6 mt-4">
<div className="input-wrapper">
<input
type="place"
id="place"
className="form-control"
value={place}
onChange={handlePlaceChange}
placeholder="Place of Birth"
/>
</div>
{/* Menampilkan error jika ada */}
{placeError && <small style={styles.SelfieImageError}>{placeError}</small>}
</div>
</div>
<div className="form-group row align-items-center my-4">
<div className="col-md-6">
<div className="input-wrapper">
<input
type="country"
id="country"
className="form-control"
value={country}
onChange={handleCountryChange}
placeholder="Country"
/>
</div>
{/* Menampilkan error jika ada */}
{countryError && <small style={styles.SelfieImageError}>{countryError}</small>}
</div>
<div className="col-md-6">
<div className="input-wrapper">
<input
type="referenceId"
id="referenceId"
className="form-control"
value={referenceId}
onChange={handleReferenceIDChange}
placeholder="Refference ID"
/>
</div>
{/* Menampilkan error jika ada */}
{referenceError && <small style={styles.SelfieImageError}>{referenceError}</small>}
</div>
</div>
<div className="form-group row align-items-center my-4">
<div className="col-md-6">
<div className="input-wrapper">
<div className="input-group">
<input
type={showInput ? 'text' : 'password'} // Change input type based on visibility
id="yourId"
className="form-control"
value={yourId}
onChange={handleYourIdChange}
placeholder="Your ID"
/>
<div className="input-group-append">
<button type="button" className="btn btn-outline-secondary" onClick={toggleInputVisibility}>
{showInput ? <FaEyeSlash /> : <FaEye />} {/* Toggle between open/closed eye */}
</button>
</div>
</div>
</div>
</div>
</div>
<div style={styles.submitButton}>
<button onClick={handleClick} className="btn d-flex justify-content-center align-items-center me-2" style={styles.submitBtn}>
<p className="text-white mb-0">Check Now</p>
</button>
</div>
</div>
</div>
</div>
</>
);
};
export default Search;

View File

@ -0,0 +1,452 @@
import React, { useState, useEffect } from 'react';
import { FaChevronLeft, FaChevronRight, FaFastBackward, FaFastForward, FaSort, FaSortUp, FaSortDown } from 'react-icons/fa'; // Icons for sorting
import { NoAvailable } from '../../../assets/icon';
// Pagination Component
const Pagination = ({ currentPage, totalPages, onPageChange }) => {
const handlePrev = () => {
if (currentPage > 1) {
onPageChange(currentPage - 1);
}
};
const handleNext = () => {
if (currentPage < totalPages) {
onPageChange(currentPage + 1);
}
};
const handleFirst = () => {
onPageChange(1); // Go to first page
};
const handleLast = () => {
onPageChange(totalPages); // Go to last page
};
// Logic to display only 3 pages in pagination
const getPaginationRange = () => {
const range = [];
const totalPagesCount = totalPages;
let start = currentPage - 1;
let end = currentPage + 1;
// Adjust start and end if near the boundaries
if (currentPage === 1) {
start = 1;
end = Math.min(3, totalPagesCount);
} else if (currentPage === totalPages) {
start = Math.max(totalPagesCount - 2, 1);
end = totalPagesCount;
}
for (let i = start; i <= end; i++) {
range.push(i);
}
return range;
};
const pageRange = getPaginationRange();
return (
<div className="pagination-container d-flex justify-content-end mt-4">
{/* First Page Button */}
<button
className="btn"
onClick={handleFirst}
disabled={currentPage === 1}
>
<FaFastBackward /> {/* Double Arrow Left */}
</button>
<button
className="btn"
onClick={handlePrev}
disabled={currentPage === 1}
>
<FaChevronLeft /> {/* Single Arrow Left */}
</button>
{/* Page Numbers */}
{pageRange.map((pageNum) => (
<button
key={pageNum}
className={`btn ${pageNum === currentPage ? 'btn-primary' : ''}`}
onClick={() => onPageChange(pageNum)}
>
{pageNum}
</button>
))}
<button
className="btn"
onClick={handleNext}
disabled={currentPage === totalPages}
>
<FaChevronRight /> {/* Single Arrow Right */}
</button>
{/* Last Page Button */}
<button
className="btn"
onClick={handleLast}
disabled={currentPage === totalPages}
>
<FaFastForward /> {/* Double Arrow Right */}
</button>
</div>
);
};
const Transaction = () => {
const [currentPage, setCurrentPage] = useState(1);
const [isMobile, setIsMobile] = useState(false); // State to detect mobile view
const [transactionData, setTransactionData] = useState([]);
const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc' }); // Sorting state
const dataPerPage = 10; // Data per page (10 data per page)
const buttonData = [
{ label: 'Copy', enabled: true },
{ label: 'CSV', enabled: true },
{ label: 'Excel', enabled: true },
{ label: 'PDF', enabled: true },
{ label: 'Print', enabled: true },
{ label: 'Column Visibility', enabled: true },
];
// Generate 691 dummy transactions
const generateDummyData = (numOfItems) => {
const transactionData = [];
for (let i = 1; i <= numOfItems; i++) {
transactionData.push({
transactionId: `TX${String(i).padStart(3, '0')}`, // Transaction ID
referenceId: `REF${String(i).padStart(3, '0')}`, // Reference ID
applicationId: `App${Math.floor(Math.random() * 5) + 1}`, // Application ID
name: `Name ${Math.floor(Math.random() * 1000) + 1}`, // Random Name
dob: new Date(1950 + Math.floor(Math.random() * 70), Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1).toLocaleDateString(), // Date of Birth (random date between 1950-2020)
createdDate: new Date(2020 + Math.floor(Math.random() * 5), Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1).toLocaleDateString(), // Created Date (random date between 2020-2024)
monitorProfile: `Profile${Math.floor(Math.random() * 10) + 1}`, // Monitor Profile (random profile ID)
mode: Math.random() > 0.5 ? 'Online' : 'Offline', // Mode (randomly Online or Offline)
status: ['Completed', 'Pending', 'Failed'][Math.floor(Math.random() * 3)], // Status (randomly selected from Completed, Pending, Failed)
});
}
return transactionData;
};
// Set the generated transaction data
useEffect(() => {
setTransactionData(generateDummyData(122)); // count data dummy transactions
}, []);
// Sorting function
const sortData = (data, config) => {
const { key, direction } = config;
return [...data].sort((a, b) => {
if (a[key] < b[key]) {
return direction === 'asc' ? -1 : 1;
}
if (a[key] > b[key]) {
return direction === 'asc' ? 1 : -1;
}
return 0;
});
};
// Handle column header sort click
const handleSort = (key) => {
let direction = 'asc';
if (sortConfig.key === key && sortConfig.direction === 'asc') {
direction = 'desc'; // Toggle direction if the same column is clicked
}
setSortConfig({ key, direction });
};
// Get the paginated data
const getPaginatedData = (data, page, perPage) => {
const sortedData = sortData(data, sortConfig);
const startIndex = (page - 1) * perPage;
const endIndex = startIndex + perPage;
return sortedData.slice(startIndex, endIndex);
};
// Handle page change
const handlePageChange = (page) => {
setCurrentPage(page);
};
// Calculate total pages based on the data and data per page
const totalPages = Math.ceil(transactionData.length / dataPerPage);
// Paginated data
const paginatedData = getPaginatedData(transactionData, currentPage, dataPerPage);
// Detect screen size and update isMobile state
useEffect(() => {
const handleResize = () => {
setIsMobile(window.innerWidth <= 768); // Change 768 to your breakpoint
};
handleResize();
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return (
<div className="container mt-5">
{/* Welcome Message */}
<div className="row-card border-left border-primary shadow mb-4" style={{ backgroundColor: '#E2FBEA' }}>
<div className="d-flex flex-column justify-content-start align-items-start p-4">
<div>
<h4 className="mb-3 text-start">
<i className="fas fa-warning fa-bold me-3"></i>Alert
</h4>
<p className="mb-0 text-start">
Get started now by creating an Application ID and explore all the demo services available on the dashboard.
Experience the ease and flexibility of trying out all our features firsthand.
</p>
</div>
</div>
</div>
<div style={styles.contentContainer}>
{/* Filter Form */}
<div className="card p-3 mb-4">
<div className="row">
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<input type="date" className="form-control" />
</div>
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<input type="date" className="form-control" />
</div>
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<select className="form-control">
<option>Select Application</option>
<option>App 1</option>
<option>App 2</option>
<option>App 3</option>
<option>App 4</option>
<option>App 5</option>
</select>
</div>
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<select className="form-control">
<option>Select Monitor</option>
<option>Monitor 1</option>
<option>Monitor 2</option>
<option>Monitor 3</option>
<option>Monitor 4</option>
<option>Monitor 5</option>
</select>
</div>
<div className={`col-12 ${isMobile ? 'mb-2' : 'col-md-2'}`}>
<select className="form-control">
<option>Select Status</option>
<option>Status 1</option>
<option>Status 2</option>
<option>Status 3</option>
<option>Status 4</option>
<option>Status 5</option>
</select>
</div>
<div className={`col-12 ${isMobile ? 'd-flex justify-content-between' : 'col-md-2 d-flex align-items-end'}`} style={{ gap: '10px' }}>
<button className="btn btn-primary w-48">Apply</button>
<button className="btn btn-secondary w-48">Cancel</button>
</div>
</div>
</div>
{/* Action Buttons */}
<div className="d-flex justify-content-between align-items-center mb-3">
<div>
{buttonData.map((button, index) =>
button.enabled ? (
<button
key={index}
className={`btn btn-light ${isMobile ? 'mb-2' : ''}`} // Add margin on mobile
style={styles.actionButton}
>
{button.label}
</button>
) : null
)}
</div>
{/* Search Bar with Icon */}
<div className="input-group" style={{ width: '250px', display: 'flex', alignItems: 'center', justifyContent: 'flex-end' }}>
<input
type="text"
placeholder="Search..."
className="form-control"
/>
<span className="input-group-text">
<i className="fas fa-search"></i> {/* FontAwesome search icon */}
</span>
</div>
</div>
{/* Table */}
<div className="table-responsive">
<table className="table table-bordered" style={styles.tableContainer}>
<thead>
<tr>
<th>No.</th> {/* Kolom untuk Nomor Urut */}
<th>
<button className="btn" onClick={() => handleSort('transactionId')}>
Transaction<br/>ID
{sortConfig.key === 'transactionId' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'transactionId' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('referenceId')}>
Reference<br/>ID
{sortConfig.key === 'referenceId' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'referenceId' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('applicationId')}>
Application<br/>ID
{sortConfig.key === 'applicationId' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'applicationId' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('name')}>
Name
{sortConfig.key === 'name' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'name' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('dob')}>
Date<br/>of Birth
{sortConfig.key === 'dob' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'dob' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('createdDate')}>
Created<br/>Date
{sortConfig.key === 'createdDate' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'createdDate' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('monitorProfile')}>
Monitor<br/>Profile
{sortConfig.key === 'monitorProfile' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'monitorProfile' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('mode')}>
Mode
{sortConfig.key === 'mode' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'mode' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
<th>
<button className="btn" onClick={() => handleSort('status')}>
Status
{sortConfig.key === 'status' &&
(sortConfig.direction === 'asc' ? <FaSortUp style={styles.iconMarginLeft} /> : <FaSortDown style={styles.iconMarginLeft} />)
}
{sortConfig.key !== 'status' && <FaSort style={styles.iconMarginLeft} />}
</button>
</th>
</tr>
</thead>
<tbody>
{paginatedData.length > 0 ? (
paginatedData.map((transaction, index) => (
<tr key={index}>
{/* Kolom Nomor Urut */}
<td>{(currentPage - 1) * dataPerPage + index + 1}</td> {/* Nomor urut berdasarkan halaman dan index */}
<td>{transaction.transactionId}</td>
<td>{transaction.referenceId}</td>
<td>{transaction.applicationId}</td>
<td>{transaction.name}</td>
<td>{transaction.dob}</td>
<td>{transaction.createdDate}</td>
<td>{transaction.monitorProfile}</td>
<td>{transaction.mode}</td>
<td>{transaction.status}</td>
</tr>
))
) : (
<tr>
<td colSpan="7" className="text-center">
<div className="d-flex flex-column align-items-center mt-5">
<img src={NoAvailable} alt="No Data Available" className="mb-3" style={styles.iconStyle} />
<p>Data not available</p>
</div>
</td>
</tr>
)}
</tbody>
</table>
</div>
{/* Pagination */}
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={handlePageChange}
/>
</div>
</div>
);
};
export default Transaction;
const styles = {
contentContainer: {
padding: '20px',
border: '0.1px solid rgba(0, 0, 0, 0.2)',
borderLeft: '4px solid #0542cc',
borderRadius: '10px',
width: '100%',
},
tableContainer: {
minHeight: '300px',
maxHeight: '1500px',
overflowY: 'auto',
},
iconStyle: {
width: '50px',
height: '50px',
},
// Add margin-left style for icons
iconMarginLeft: {
verticalAlign: 'middle',
marginLeft: '0.5rem',
},
};

View File

@ -0,0 +1,385 @@
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import Select from 'react-select';
const BASE_URL = process.env.REACT_APP_BASE_URL;
const API_KEY = process.env.REACT_APP_API_KEY;
const Verify = () => {
const [isLoading, setIsLoading] = useState(false);
const [applicationId, setApplicationId] = useState('');
const [inputValueApplication, setInputValueApplication] = useState('');
const [applicationIds, setApplicationIds] = useState([]);
const [selectedQuota, setSelectedQuota] = useState(0);
const [fullName, setFullName] = useState('');
const [fullNameError, setFullNameError] = useState('');
const [date, setDate] = useState('');
const [dateError, setDateError] = useState('');
const [place, setPlace] = useState('');
const [placeError, setPlaceError] = useState('');
const [country, setCountry] = useState('');
const [countryError, setCountryError] = useState('');
const [isMobile, setIsMobile] = useState(false);
// Fungsi untuk menangani perubahan pada NPWP
const handleFullNameChange = (e) => {
setFullName(e.target.value);
};
const handleDateChange = (e) => {
setDate(e.target.value);
};
const handlePlaceChange = (e) => {
setPlace(e.target.value);
};
const handleCountryChange = (e) => {
setCountry(e.target.value);
};
const applicationOptions = applicationIds.map(app => ({
value: app.id,
label: app.name
}));
const handleApplicationChange = (selectedOption) => {
if (selectedOption) {
const selectedId = selectedOption.value;
const selectedApp = applicationIds.find(app => app.id === parseInt(selectedId));
if (selectedApp) {
setApplicationId(selectedId);
setSelectedQuota(selectedApp.quota); // Set the selected quota
}
}
};
const handleInputChangeApplication = (newInputValue) => {
if (newInputValue.length <= 15) {
setInputValueApplication(newInputValue);
}
};
const fetchApplicationIds = async () => {
try {
const response = await fetch(`${BASE_URL}/application/list`, {
method: 'GET',
headers: {
'accept': 'application/json',
'x-api-key': API_KEY,
},
});
const data = await response.json();
return data.details.data;
} catch (error) {
console.error('Error fetching application IDs:', error);
return [];
}
};
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
const data = await fetchApplicationIds();
setApplicationIds(data);
setIsLoading(false);
};
fetchData();
const checkIfMobile = () => {
setIsMobile(window.innerWidth <= 768);
};
checkIfMobile();
window.addEventListener('resize', checkIfMobile);
return () => window.removeEventListener('resize', checkIfMobile);
}, []);
const handleClick = () => {
setSelectedQuota(prevQuota => prevQuota - 1); // Mengurangi nilai selectedQuota
console.log('Click Check Now!'); // Tampilkan log pada klik
};
const styles = {
container: {
padding: '1rem',
width: '100%',
margin: '0 auto',
boxSizing: 'border-box',
},
wrapper: {
display: 'flex',
flexDirection: 'column',
borderRadius: '8px',
padding: '1.5rem',
maxWidth: '1200px',
margin: '0 auto',
},
wrapperMobile: {
display: 'flex',
flexDirection: 'column',
borderRadius: '8px',
padding: '1rem',
},
breadcrumb: {
marginBottom: '1rem',
fontSize: '0.9rem',
},
breadcrumbLink: {
textDecoration: 'none',
color: '#0542cc',
},
tabLink: {
padding: '10px 20px',
},
welcomeCard: {
backgroundColor: '#E2FBEA',
borderLeft: '4px solid #0542CC',
borderRadius: '5px',
marginBottom: '20px',
},
createButton: {
backgroundColor: '#0542CC',
},
section: {
padding: '20px',
border: '0.1px solid rgba(0, 0, 0, 0.2)',
borderLeft: '4px solid #0542CC',
borderRadius: '10px',
width: '100%',
},
tabLink: {
padding: '10px 20px',
},
text: {
fontSize: '16px',
fontWeight: '400',
margin: '0',
marginTop: '8px',
},
remainingQuotaWrapper: {
marginTop: '2vh',
},
remainingQuota: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
marginTop: '4px',
},
quotaText: {
fontSize: '40px',
color: '#0542cc',
fontWeight: '600',
},
timesText: {
marginLeft: '8px',
verticalAlign: 'super',
fontSize: '20px',
},
submitButton: {
marginLeft: 'auto',
marginTop: '4rem',
textAlign: 'start',
position: 'relative',
zIndex: 1,
},
submitBtn: {
backgroundColor: '#0542CC',
},
loadingOverlay: {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.2)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: 1000,
},
spinner: {
border: '4px solid rgba(0, 0, 0, 0.1)',
borderLeftColor: '#0542cc',
borderRadius: '50%',
width: '90px',
height: '90px',
animation: 'spin 1s ease-in-out infinite',
},
select: {
width: '100%',
paddingRight: '30px',
fontSize: '16px',
border: '1px solid #ccc',
outline: 'none',
},
OtpId: {
marginTop: isMobile ? '2vh' : '0',
},
PhoneNumber: {
marginTop: isMobile ? '2vh' : '-12vh',
},
textarea: {
width: '100%',
height: '200px',
resize: 'none',
border: '1px solid #ccc',
outline: 'none',
},
characterCount: {
display: 'flex',
justifyContent: 'flex-end',
marginTop: '8px',
},
};
return (
<>
<style>
{`
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`}
</style>
{isLoading && (
<div style={styles.loadingOverlay}>
<div style={styles.spinner}></div>
</div>
)}
<div style={styles.container}>
<div style={isMobile ? styles.wrapperMobile : styles.wrapper}>
{/* Static Content */}
<div className="row-card border-left border-primary shadow mb-4" style={styles.welcomeCard}>
<div className="d-flex flex-column justify-content-start align-items-start p-4">
<h4 className="mb-3 text-start">
<i className="fas fa-warning fa-bold me-3"></i>Alert
</h4>
<p className="mb-0 text-start">
Get started now by creating an Application ID and explore all the demo services available on the dashboard.
Experience the ease and flexibility of trying out all our features firsthand.
</p>
<div className="d-flex flex-row mt-3">
<Link to="/createApps" style={{ textDecoration: 'none' }}>
<button className="btn d-flex justify-content-center align-items-center me-2" style={styles.createButton}>
<i className="fas fa-plus text-white me-2"></i>
<p className="text-white mb-0">Create New App ID</p>
</button>
</Link>
</div>
</div>
</div>
<div style={styles.section}>
<div className="form-group row align-items-center">
<div className="col-md-6">
<Select
id="applicationId"
value={applicationOptions.find(option => option.value === applicationId)}
onChange={handleApplicationChange}
options={applicationOptions}
placeholder="Select Application ID"
isSearchable
menuPortalTarget={document.body}
menuPlacement="auto"
inputValue={inputValueApplication}
onInputChange={handleInputChangeApplication}
/>
</div>
<div className="col-md-6" style={styles.remainingQuotaWrapper}>
<p style={styles.text}>Remaining Quota</p>
<div style={styles.remainingQuota}>
<span style={styles.quotaText}>{selectedQuota}</span>
<span style={styles.timesText}>(times)</span>
</div>
</div>
</div>
{/* Nik and Date Row */}
<div className="form-group row align-items-center">
{/* Kolom untuk input email */}
<div className="col-md-6 mt-4">
<div className="input-wrapper">
<input
type="fullName"
id="fullName"
className="form-control"
value={fullName}
onChange={handleFullNameChange}
placeholder="Full Name"
/>
</div>
{/* Menampilkan error jika ada */}
{fullNameError && <small style={styles.SelfieImageError}>{fullNameError}</small>}
</div>
{/* Kolom untuk input tanggal */}
<div className="col-md-6">
<div className="input-wrapper">
<label htmlFor="date" style={{ display: 'block', marginBottom: '5px' }}>
Date of Birth
</label>
<input
type="date"
id="date"
className="form-control"
value={date}
onChange={handleDateChange}
/>
</div>
{/* Menampilkan error jika ada */}
{dateError && <small style={styles.SelfieImageError}>{dateError}</small>}
</div>
</div>
{/* Name and Phone */}
<div className="form-group row align-items-center my-4">
<div className="col-md-6">
<div className="input-wrapper">
<input
type="place"
id="place"
className="form-control"
value={place}
onChange={handlePlaceChange}
placeholder="Place of Birth"
/>
</div>
{/* Menampilkan error jika ada */}
{placeError && <small style={styles.SelfieImageError}>{placeError}</small>}
</div>
<div className="col-md-6">
<div className="input-wrapper">
<input
type="country"
id="country"
className="form-control"
value={country}
onChange={handleCountryChange}
placeholder="Country"
/>
</div>
{/* Menampilkan error jika ada */}
{countryError && <small style={styles.SelfieImageError}>{countryError}</small>}
</div>
</div>
<div style={styles.submitButton}>
<button onClick={handleClick} className="btn d-flex justify-content-center align-items-center me-2" style={styles.submitBtn}>
<p className="text-white mb-0">Make a Demo</p>
</button>
</div>
</div>
</div>
</div>
</>
);
};
export default Verify;

View File

@ -0,0 +1,13 @@
import Verify from "./Verify";
import Admin from "./Admin";
import Search from './Search';
import Transaction from './Transaction';
import Monitor from './Monitor';
export {
Verify,
Admin,
Search,
Transaction,
Monitor
}