Slicing UI/UX
This commit is contained in:
parent
6c115abf48
commit
2a2662df25
199
package-lock.json
generated
199
package-lock.json
generated
@ -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",
|
||||||
|
@ -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",
|
||||||
|
85
src/App.js
85
src/App.js
@ -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>
|
||||||
|
@ -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',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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'}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -7,5 +7,5 @@ export {
|
|||||||
Navbar,
|
Navbar,
|
||||||
Sidebar,
|
Sidebar,
|
||||||
Main,
|
Main,
|
||||||
Footer
|
Footer,
|
||||||
}
|
}
|
411
src/screens/Identity/Electric/Transaction.jsx
Normal file
411
src/screens/Identity/Electric/Transaction.jsx
Normal 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
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
843
src/screens/Identity/Electric/Verify.jsx
Normal file
843
src/screens/Identity/Electric/Verify.jsx
Normal 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',
|
||||||
|
},
|
||||||
|
};
|
7
src/screens/Identity/Electric/index.js
Normal file
7
src/screens/Identity/Electric/index.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import Transaction from './Transaction';
|
||||||
|
import Verify from './Verify';
|
||||||
|
|
||||||
|
export {
|
||||||
|
Transaction,
|
||||||
|
Verify
|
||||||
|
}
|
411
src/screens/Identity/IdVerif/Transaction.jsx
Normal file
411
src/screens/Identity/IdVerif/Transaction.jsx
Normal 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
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
359
src/screens/Identity/IdVerif/Verify.jsx
Normal file
359
src/screens/Identity/IdVerif/Verify.jsx
Normal 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;
|
7
src/screens/Identity/IdVerif/index.js
Normal file
7
src/screens/Identity/IdVerif/index.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import Verify from "./Verify";
|
||||||
|
import Transaction from './Transaction';
|
||||||
|
|
||||||
|
export {
|
||||||
|
Verify,
|
||||||
|
Transaction
|
||||||
|
}
|
411
src/screens/Identity/Income/Transaction.jsx
Normal file
411
src/screens/Identity/Income/Transaction.jsx
Normal 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
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
449
src/screens/Identity/Income/Verify.jsx
Normal file
449
src/screens/Identity/Income/Verify.jsx
Normal 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;
|
7
src/screens/Identity/Income/index.js
Normal file
7
src/screens/Identity/Income/index.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import Verify from "./Verify";
|
||||||
|
import Transaction from './Transaction';
|
||||||
|
|
||||||
|
export {
|
||||||
|
Verify,
|
||||||
|
Transaction
|
||||||
|
}
|
400
src/screens/Identity/Npwp/Transaction.jsx
Normal file
400
src/screens/Identity/Npwp/Transaction.jsx
Normal 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
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
5
src/screens/Identity/Npwp/index.js
Normal file
5
src/screens/Identity/Npwp/index.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import Transaction from './Transaction';
|
||||||
|
|
||||||
|
export {
|
||||||
|
Transaction
|
||||||
|
}
|
288
src/screens/Identity/Tax/Section/Nik.jsx
Normal file
288
src/screens/Identity/Tax/Section/Nik.jsx
Normal 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;
|
||||||
|
|
254
src/screens/Identity/Tax/Section/Npwp.jsx
Normal file
254
src/screens/Identity/Tax/Section/Npwp.jsx
Normal 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;
|
||||||
|
|
7
src/screens/Identity/Tax/Section/index.js
Normal file
7
src/screens/Identity/Tax/Section/index.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import Nik from './Nik';
|
||||||
|
import Npwp from './Npwp';
|
||||||
|
|
||||||
|
export {
|
||||||
|
Nik,
|
||||||
|
Npwp
|
||||||
|
}
|
411
src/screens/Identity/Tax/Transaction.jsx
Normal file
411
src/screens/Identity/Tax/Transaction.jsx
Normal 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
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
114
src/screens/Identity/Tax/Verify.jsx
Normal file
114
src/screens/Identity/Tax/Verify.jsx
Normal 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',
|
||||||
|
},
|
||||||
|
};
|
7
src/screens/Identity/Tax/index.js
Normal file
7
src/screens/Identity/Tax/index.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import Verify from './Verify';
|
||||||
|
import Transaction from './Transaction';
|
||||||
|
|
||||||
|
export {
|
||||||
|
Verify,
|
||||||
|
Transaction
|
||||||
|
}
|
278
src/screens/Profile/Account.jsx
Normal file
278
src/screens/Profile/Account.jsx
Normal 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;
|
1426
src/screens/Profile/Company.jsx
Normal file
1426
src/screens/Profile/Company.jsx
Normal file
File diff suppressed because it is too large
Load Diff
11
src/screens/Profile/Production.jsx
Normal file
11
src/screens/Profile/Production.jsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const Production = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Production</h1>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Production
|
11
src/screens/Profile/Trial.jsx
Normal file
11
src/screens/Profile/Trial.jsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const Trial = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Trial</h1>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Trial
|
11
src/screens/Profile/index.js
Normal file
11
src/screens/Profile/index.js
Normal 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
|
||||||
|
}
|
200
src/screens/Watchlist/Screening/Admin.jsx
Normal file
200
src/screens/Watchlist/Screening/Admin.jsx
Normal 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;
|
||||||
|
|
398
src/screens/Watchlist/Screening/Monitor.jsx
Normal file
398
src/screens/Watchlist/Screening/Monitor.jsx
Normal 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',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
420
src/screens/Watchlist/Screening/Search.jsx
Normal file
420
src/screens/Watchlist/Screening/Search.jsx
Normal 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;
|
||||||
|
|
452
src/screens/Watchlist/Screening/Transaction.jsx
Normal file
452
src/screens/Watchlist/Screening/Transaction.jsx
Normal 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',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
385
src/screens/Watchlist/Screening/Verify.jsx
Normal file
385
src/screens/Watchlist/Screening/Verify.jsx
Normal 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;
|
||||||
|
|
13
src/screens/Watchlist/Screening/index.js
Normal file
13
src/screens/Watchlist/Screening/index.js
Normal 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
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user