| @@ -11,6 +11,7 @@ | |||
| "@emotion/cache": "^11.11.0", | |||
| "@emotion/react": "^11.11.1", | |||
| "@emotion/styled": "^11.11.0", | |||
| "@faker-js/faker": "^8.4.1", | |||
| "@fontsource/inter": "^5.0.16", | |||
| "@fontsource/plus-jakarta-sans": "^5.0.18", | |||
| "@mui/icons-material": "^5.15.0", | |||
| @@ -20,6 +21,7 @@ | |||
| "@mui/x-date-pickers": "^6.18.7", | |||
| "@unly/universal-language-detector": "^2.0.3", | |||
| "apexcharts": "^3.45.2", | |||
| "axios": "^1.6.8", | |||
| "dayjs": "^1.11.10", | |||
| "i18next": "^23.7.11", | |||
| "i18next-resources-to-backend": "^1.2.0", | |||
| @@ -37,7 +39,8 @@ | |||
| "react-select": "^5.8.0", | |||
| "reactstrap": "^9.2.2", | |||
| "styled-components": "^6.1.8", | |||
| "sweetalert2": "^11.10.3" | |||
| "sweetalert2": "^11.10.3", | |||
| "xlsx-js-style": "^1.2.0" | |||
| }, | |||
| "devDependencies": { | |||
| "@types/lodash": "^4.14.202", | |||
| @@ -1933,6 +1936,21 @@ | |||
| "node": "^12.22.0 || ^14.17.0 || >=16.0.0" | |||
| } | |||
| }, | |||
| "node_modules/@faker-js/faker": { | |||
| "version": "8.4.1", | |||
| "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.1.tgz", | |||
| "integrity": "sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==", | |||
| "funding": [ | |||
| { | |||
| "type": "opencollective", | |||
| "url": "https://opencollective.com/fakerjs" | |||
| } | |||
| ], | |||
| "engines": { | |||
| "node": "^14.17.0 || ^16.13.0 || >=18.0.0", | |||
| "npm": ">=6.14.13" | |||
| } | |||
| }, | |||
| "node_modules/@floating-ui/core": { | |||
| "version": "1.6.0", | |||
| "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", | |||
| @@ -3530,6 +3548,21 @@ | |||
| "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" | |||
| } | |||
| }, | |||
| "node_modules/adler-32": { | |||
| "version": "1.2.0", | |||
| "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.2.0.tgz", | |||
| "integrity": "sha512-/vUqU/UY4MVeFsg+SsK6c+/05RZXIHZMGJA+PX5JyWI0ZRcBpupnRuPLU/NXXoFwMYCPCoxIfElM2eS+DUXCqQ==", | |||
| "dependencies": { | |||
| "exit-on-epipe": "~1.0.1", | |||
| "printj": "~1.1.0" | |||
| }, | |||
| "bin": { | |||
| "adler32": "bin/adler32.njs" | |||
| }, | |||
| "engines": { | |||
| "node": ">=0.8" | |||
| } | |||
| }, | |||
| "node_modules/ajv": { | |||
| "version": "6.12.6", | |||
| "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", | |||
| @@ -3836,6 +3869,11 @@ | |||
| "has-symbols": "^1.0.3" | |||
| } | |||
| }, | |||
| "node_modules/asynckit": { | |||
| "version": "0.4.0", | |||
| "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", | |||
| "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" | |||
| }, | |||
| "node_modules/at-least-node": { | |||
| "version": "1.0.0", | |||
| "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", | |||
| @@ -3904,6 +3942,16 @@ | |||
| "node": ">=4" | |||
| } | |||
| }, | |||
| "node_modules/axios": { | |||
| "version": "1.6.8", | |||
| "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", | |||
| "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", | |||
| "dependencies": { | |||
| "follow-redirects": "^1.15.6", | |||
| "form-data": "^4.0.0", | |||
| "proxy-from-env": "^1.1.0" | |||
| } | |||
| }, | |||
| "node_modules/axobject-query": { | |||
| "version": "3.2.1", | |||
| "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", | |||
| @@ -4150,6 +4198,26 @@ | |||
| } | |||
| ] | |||
| }, | |||
| "node_modules/cfb": { | |||
| "version": "1.2.2", | |||
| "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", | |||
| "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", | |||
| "dependencies": { | |||
| "adler-32": "~1.3.0", | |||
| "crc-32": "~1.2.0" | |||
| }, | |||
| "engines": { | |||
| "node": ">=0.8" | |||
| } | |||
| }, | |||
| "node_modules/cfb/node_modules/adler-32": { | |||
| "version": "1.3.1", | |||
| "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", | |||
| "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", | |||
| "engines": { | |||
| "node": ">=0.8" | |||
| } | |||
| }, | |||
| "node_modules/chalk": { | |||
| "version": "2.4.2", | |||
| "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", | |||
| @@ -4236,6 +4304,26 @@ | |||
| "node": ">=6" | |||
| } | |||
| }, | |||
| "node_modules/codepage": { | |||
| "version": "1.14.0", | |||
| "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.14.0.tgz", | |||
| "integrity": "sha512-iz3zJLhlrg37/gYRWgEPkaFTtzmnEv1h+r7NgZum2lFElYQPi0/5bnmuDfODHxfp0INEfnRqyfyeIJDbb7ahRw==", | |||
| "dependencies": { | |||
| "commander": "~2.14.1", | |||
| "exit-on-epipe": "~1.0.1" | |||
| }, | |||
| "bin": { | |||
| "codepage": "bin/codepage.njs" | |||
| }, | |||
| "engines": { | |||
| "node": ">=0.8" | |||
| } | |||
| }, | |||
| "node_modules/codepage/node_modules/commander": { | |||
| "version": "2.14.1", | |||
| "resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz", | |||
| "integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==" | |||
| }, | |||
| "node_modules/color-convert": { | |||
| "version": "1.9.3", | |||
| "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", | |||
| @@ -4249,6 +4337,17 @@ | |||
| "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", | |||
| "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" | |||
| }, | |||
| "node_modules/combined-stream": { | |||
| "version": "1.0.8", | |||
| "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", | |||
| "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", | |||
| "dependencies": { | |||
| "delayed-stream": "~1.0.0" | |||
| }, | |||
| "engines": { | |||
| "node": ">= 0.8" | |||
| } | |||
| }, | |||
| "node_modules/commander": { | |||
| "version": "4.1.1", | |||
| "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", | |||
| @@ -4316,6 +4415,17 @@ | |||
| "node": ">=10" | |||
| } | |||
| }, | |||
| "node_modules/crc-32": { | |||
| "version": "1.2.2", | |||
| "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", | |||
| "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", | |||
| "bin": { | |||
| "crc32": "bin/crc32.njs" | |||
| }, | |||
| "engines": { | |||
| "node": ">=0.8" | |||
| } | |||
| }, | |||
| "node_modules/cross-spawn": { | |||
| "version": "7.0.3", | |||
| "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", | |||
| @@ -4516,6 +4626,14 @@ | |||
| "rimraf": "bin.js" | |||
| } | |||
| }, | |||
| "node_modules/delayed-stream": { | |||
| "version": "1.0.0", | |||
| "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", | |||
| "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", | |||
| "engines": { | |||
| "node": ">=0.4.0" | |||
| } | |||
| }, | |||
| "node_modules/dequal": { | |||
| "version": "2.0.3", | |||
| "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", | |||
| @@ -5431,6 +5549,14 @@ | |||
| "node": ">=0.8.x" | |||
| } | |||
| }, | |||
| "node_modules/exit-on-epipe": { | |||
| "version": "1.0.1", | |||
| "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", | |||
| "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", | |||
| "engines": { | |||
| "node": ">=0.8" | |||
| } | |||
| }, | |||
| "node_modules/fast-deep-equal": { | |||
| "version": "3.1.3", | |||
| "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", | |||
| @@ -5476,6 +5602,11 @@ | |||
| "reusify": "^1.0.4" | |||
| } | |||
| }, | |||
| "node_modules/fflate": { | |||
| "version": "0.3.11", | |||
| "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.3.11.tgz", | |||
| "integrity": "sha512-Rr5QlUeGN1mbOHlaqcSYMKVpPbgLy0AWT/W0EHxA6NGI12yO1jpoui2zBBvU2G824ltM6Ut8BFgfHSBGfkmS0A==" | |||
| }, | |||
| "node_modules/file-entry-cache": { | |||
| "version": "6.0.1", | |||
| "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", | |||
| @@ -5575,6 +5706,25 @@ | |||
| "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", | |||
| "dev": true | |||
| }, | |||
| "node_modules/follow-redirects": { | |||
| "version": "1.15.6", | |||
| "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", | |||
| "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", | |||
| "funding": [ | |||
| { | |||
| "type": "individual", | |||
| "url": "https://github.com/sponsors/RubenVerborgh" | |||
| } | |||
| ], | |||
| "engines": { | |||
| "node": ">=4.0" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "debug": { | |||
| "optional": true | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/for-each": { | |||
| "version": "0.3.3", | |||
| "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", | |||
| @@ -5599,6 +5749,27 @@ | |||
| "url": "https://github.com/sponsors/isaacs" | |||
| } | |||
| }, | |||
| "node_modules/form-data": { | |||
| "version": "4.0.0", | |||
| "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", | |||
| "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", | |||
| "dependencies": { | |||
| "asynckit": "^0.4.0", | |||
| "combined-stream": "^1.0.8", | |||
| "mime-types": "^2.1.12" | |||
| }, | |||
| "engines": { | |||
| "node": ">= 6" | |||
| } | |||
| }, | |||
| "node_modules/frac": { | |||
| "version": "1.1.2", | |||
| "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", | |||
| "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", | |||
| "engines": { | |||
| "node": ">=0.8" | |||
| } | |||
| }, | |||
| "node_modules/fraction.js": { | |||
| "version": "4.3.7", | |||
| "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", | |||
| @@ -6977,7 +7148,6 @@ | |||
| "version": "1.52.0", | |||
| "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", | |||
| "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", | |||
| "peer": true, | |||
| "engines": { | |||
| "node": ">= 0.6" | |||
| } | |||
| @@ -6986,7 +7156,6 @@ | |||
| "version": "2.1.35", | |||
| "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", | |||
| "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", | |||
| "peer": true, | |||
| "dependencies": { | |||
| "mime-db": "1.52.0" | |||
| }, | |||
| @@ -7880,6 +8049,17 @@ | |||
| "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", | |||
| "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" | |||
| }, | |||
| "node_modules/printj": { | |||
| "version": "1.1.2", | |||
| "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", | |||
| "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==", | |||
| "bin": { | |||
| "printj": "bin/printj.njs" | |||
| }, | |||
| "engines": { | |||
| "node": ">=0.8" | |||
| } | |||
| }, | |||
| "node_modules/prop-types": { | |||
| "version": "15.8.1", | |||
| "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", | |||
| @@ -7890,6 +8070,11 @@ | |||
| "react-is": "^16.13.1" | |||
| } | |||
| }, | |||
| "node_modules/proxy-from-env": { | |||
| "version": "1.1.0", | |||
| "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", | |||
| "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" | |||
| }, | |||
| "node_modules/punycode": { | |||
| "version": "2.3.1", | |||
| "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", | |||
| @@ -8631,6 +8816,17 @@ | |||
| "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", | |||
| "deprecated": "Please use @jridgewell/sourcemap-codec instead" | |||
| }, | |||
| "node_modules/ssf": { | |||
| "version": "0.11.2", | |||
| "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", | |||
| "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", | |||
| "dependencies": { | |||
| "frac": "~1.1.2" | |||
| }, | |||
| "engines": { | |||
| "node": ">=0.8" | |||
| } | |||
| }, | |||
| "node_modules/streamsearch": { | |||
| "version": "1.1.0", | |||
| "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", | |||
| @@ -9853,6 +10049,22 @@ | |||
| "url": "https://github.com/sponsors/ljharb" | |||
| } | |||
| }, | |||
| "node_modules/wmf": { | |||
| "version": "1.0.2", | |||
| "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", | |||
| "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", | |||
| "engines": { | |||
| "node": ">=0.8" | |||
| } | |||
| }, | |||
| "node_modules/word": { | |||
| "version": "0.3.0", | |||
| "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", | |||
| "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", | |||
| "engines": { | |||
| "node": ">=0.8" | |||
| } | |||
| }, | |||
| "node_modules/workbox-background-sync": { | |||
| "version": "6.6.0", | |||
| "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.6.0.tgz", | |||
| @@ -10238,6 +10450,34 @@ | |||
| "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", | |||
| "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" | |||
| }, | |||
| "node_modules/xlsx-js-style": { | |||
| "version": "1.2.0", | |||
| "resolved": "https://registry.npmjs.org/xlsx-js-style/-/xlsx-js-style-1.2.0.tgz", | |||
| "integrity": "sha512-DDT4FXFSWfT4DXMSok/m3TvmP1gvO3dn0Eu/c+eXHW5Kzmp7IczNkxg/iEPnImbG9X0Vb8QhROda5eatSR/97Q==", | |||
| "dependencies": { | |||
| "adler-32": "~1.2.0", | |||
| "cfb": "^1.1.4", | |||
| "codepage": "~1.14.0", | |||
| "commander": "~2.17.1", | |||
| "crc-32": "~1.2.0", | |||
| "exit-on-epipe": "~1.0.1", | |||
| "fflate": "^0.3.8", | |||
| "ssf": "~0.11.2", | |||
| "wmf": "~1.0.1", | |||
| "word": "~0.3.0" | |||
| }, | |||
| "bin": { | |||
| "xlsx": "bin/xlsx.njs" | |||
| }, | |||
| "engines": { | |||
| "node": ">=0.8" | |||
| } | |||
| }, | |||
| "node_modules/xlsx-js-style/node_modules/commander": { | |||
| "version": "2.17.1", | |||
| "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", | |||
| "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" | |||
| }, | |||
| "node_modules/yallist": { | |||
| "version": "4.0.0", | |||
| "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", | |||
| @@ -0,0 +1,358 @@ | |||
| //src\components\ReportSearchBox\SearchBox.tsx | |||
| "use client"; | |||
| import Grid from "@mui/material/Grid"; | |||
| import Card from "@mui/material/Card"; | |||
| import CardContent from "@mui/material/CardContent"; | |||
| import Typography from "@mui/material/Typography"; | |||
| import React, { useCallback, useMemo, useState } from "react"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import TextField from "@mui/material/TextField"; | |||
| import FormControl from "@mui/material/FormControl"; | |||
| import InputLabel from "@mui/material/InputLabel"; | |||
| import Select, { SelectChangeEvent } from "@mui/material/Select"; | |||
| import MenuItem from "@mui/material/MenuItem"; | |||
| import CardActions from "@mui/material/CardActions"; | |||
| import Button from "@mui/material/Button"; | |||
| import RestartAlt from "@mui/icons-material/RestartAlt"; | |||
| import Search from "@mui/icons-material/Search"; | |||
| import dayjs from "dayjs"; | |||
| import "dayjs/locale/zh-hk"; | |||
| import { DatePicker } from "@mui/x-date-pickers/DatePicker"; | |||
| import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; | |||
| import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | |||
| import { Box } from "@mui/material"; | |||
| import * as XLSX from 'xlsx-js-style'; | |||
| //import { DownloadReportButton } from '../LateStartReportGen/DownloadReportButton'; | |||
| interface BaseCriterion<T extends string> { | |||
| label: string; | |||
| label2?: string; | |||
| paramName: T; | |||
| paramName2?: T; | |||
| } | |||
| interface TextCriterion<T extends string> extends BaseCriterion<T> { | |||
| type: "text"; | |||
| } | |||
| interface SelectCriterion<T extends string> extends BaseCriterion<T> { | |||
| type: "select"; | |||
| options: string[]; | |||
| } | |||
| interface DateRangeCriterion<T extends string> extends BaseCriterion<T> { | |||
| type: "dateRange"; | |||
| } | |||
| export type Criterion<T extends string> = | |||
| | TextCriterion<T> | |||
| | SelectCriterion<T> | |||
| | DateRangeCriterion<T>; | |||
| interface Props<T extends string> { | |||
| criteria: Criterion<T>[]; | |||
| onSearch: (inputs: Record<T, string>) => void; | |||
| onReset?: () => void; | |||
| } | |||
| function SearchBox<T extends string>({ | |||
| criteria, | |||
| onSearch, | |||
| onReset, | |||
| }: Props<T>) { | |||
| const { t } = useTranslation("common"); | |||
| const defaultInputs = useMemo( | |||
| () => | |||
| criteria.reduce<Record<T, string>>( | |||
| (acc, c) => { | |||
| return { ...acc, [c.paramName]: c.type === "select" ? "All" : "" }; | |||
| }, | |||
| {} as Record<T, string>, | |||
| ), | |||
| [criteria], | |||
| ); | |||
| const [inputs, setInputs] = useState(defaultInputs); | |||
| const makeInputChangeHandler = useCallback( | |||
| (paramName: T): React.ChangeEventHandler<HTMLInputElement> => { | |||
| return (e) => { | |||
| setInputs((i) => ({ ...i, [paramName]: e.target.value })); | |||
| }; | |||
| }, | |||
| [], | |||
| ); | |||
| const makeSelectChangeHandler = useCallback((paramName: T) => { | |||
| return (e: SelectChangeEvent) => { | |||
| setInputs((i) => ({ ...i, [paramName]: e.target.value })); | |||
| }; | |||
| }, []); | |||
| const makeDateChangeHandler = useCallback((paramName: T) => { | |||
| return (e: any) => { | |||
| setInputs((i) => ({ ...i, [paramName]: dayjs(e).format("YYYY-MM-DD") })); | |||
| }; | |||
| }, []); | |||
| const makeDateToChangeHandler = useCallback((paramName: T) => { | |||
| return (e: any) => { | |||
| setInputs((i) => ({ | |||
| ...i, | |||
| [paramName + "To"]: dayjs(e).format("YYYY-MM-DD"), | |||
| })); | |||
| }; | |||
| }, []); | |||
| const handleReset = () => { | |||
| setInputs(defaultInputs); | |||
| onReset?.(); | |||
| }; | |||
| const handleSearch = () => { | |||
| onSearch(inputs); | |||
| }; | |||
| const handleDownload = async () => { | |||
| //setIsLoading(true); | |||
| try { | |||
| const response = await fetch('/temp/AR08_Project P&L Report.xlsx', { | |||
| headers: { | |||
| 'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', | |||
| }, | |||
| }); | |||
| if (!response.ok) throw new Error('Network response was not ok.'); | |||
| const data = await response.blob(); | |||
| const reader = new FileReader(); | |||
| reader.onload = (e) => { | |||
| if (e.target && e.target.result) { | |||
| const ab = e.target.result as ArrayBuffer; | |||
| const workbook = XLSX.read(ab, { type: 'array' }); | |||
| const firstSheetName = workbook.SheetNames[0]; | |||
| const worksheet = workbook.Sheets[firstSheetName]; | |||
| // Add the current date to cell B2 | |||
| const cellAddress = 'B2'; | |||
| const date = new Date().toISOString().split('T')[0]; // Format YYYY-MM-DD | |||
| const formattedDate = date.replace(/-/g, '/'); // Change format to YYYY/MM/DD | |||
| XLSX.utils.sheet_add_aoa(worksheet, [[formattedDate]], { origin: cellAddress }); | |||
| // Style for cell A1: Font size 16 and bold | |||
| if (worksheet['A1']) { | |||
| worksheet['A1'].s = { | |||
| font: {bold: true,sz: 16, // Font size 16 | |||
| alignment: { horizontal: 'left' }, | |||
| //name: 'Times New Roman' // Specify font | |||
| } | |||
| }; | |||
| } | |||
| // Apply styles from A2 to A7 (bold) | |||
| ['A2', 'A3', 'A4','A5', 'A6', 'A7'].forEach(cell => { | |||
| if (worksheet[cell]) { | |||
| worksheet[cell].s = { font: { bold: true,sz: 12, | |||
| alignment: { horizontal: 'left' }, } | |||
| }; | |||
| } | |||
| }); | |||
| const firstTableData = [ | |||
| ['Column1', 'Column2', 'Column3'], // Row 1 | |||
| ['Data1', 'Data2', 'Data3'], // Row 2 | |||
| // ... more rows as needed | |||
| ]; | |||
| const secondTableData = [ | |||
| ['Column1', 'Column2', 'Column3'], // Row 1 of second table | |||
| ['Data1', 'Data2', 'Data3'], // Row 2 of second table | |||
| // ... more rows as needed | |||
| ]; | |||
| // Find the last row of the first table | |||
| let lastRowOfFirstTable = 10; // Starting row for data in the first table | |||
| while (worksheet[XLSX.utils.encode_cell({ c: 0, r: lastRowOfFirstTable })]) { | |||
| lastRowOfFirstTable++; | |||
| } | |||
| // Insert the first data form into the worksheet at the desired location | |||
| XLSX.utils.sheet_add_aoa(worksheet, firstTableData, { origin: { c: 0, r: lastRowOfFirstTable } }); | |||
| // Update lastRowOfFirstTable to account for the new data | |||
| lastRowOfFirstTable += firstTableData.length; | |||
| // Now insert the text that goes between the two tables | |||
| // // Insert the additional text with one row of spacing after the first table | |||
| const textRow = lastRowOfFirstTable + 1; // Adjust the 1 based on how many lines of spacing you want | |||
| XLSX.utils.sheet_add_aoa(worksheet, [['Staff No. and Name']], { origin: { c: 0, r: textRow+1 } }); | |||
| let secondTableStartRow = textRow + 3; | |||
| // Insert the second data form into the worksheet at the new starting row | |||
| XLSX.utils.sheet_add_aoa(worksheet, secondTableData, { origin: { c: 0, r: secondTableStartRow } }); | |||
| // Source cell coordinates | |||
| const sourceCellCoord = { c: 1, r: 2 }; // C3 (columns and rows are 0-indexed in this library) | |||
| // Target cell coordinates | |||
| const targetCellCoord = { c: 3, r: 9 }; | |||
| // Create references for source and target cells | |||
| const sourceCellRef = XLSX.utils.encode_cell(sourceCellCoord); | |||
| const targetCellRef = XLSX.utils.encode_cell(targetCellCoord); | |||
| // Copy the cell content from C3 to the target cell | |||
| if (worksheet[sourceCellRef]) { | |||
| worksheet[targetCellRef] = { ...worksheet[sourceCellRef] }; | |||
| // If the source cell has a style, deep clone it for the target cell | |||
| if (worksheet[sourceCellRef].s) { | |||
| worksheet[targetCellRef].s = JSON.parse(JSON.stringify(worksheet[sourceCellRef].s)); | |||
| } | |||
| } | |||
| // Formatting from A10 to F10 | |||
| // Apply styles from A10 to F10 (bottom border, center alignment) | |||
| for (let col = 0; col < 7; col++) { // Columns A to G | |||
| const cellRef = XLSX.utils.encode_col(col) + '10';//row | |||
| if (worksheet[cellRef]) { | |||
| worksheet[cellRef].s = { | |||
| font: { bold: false }, | |||
| alignment: { horizontal: 'center' }, | |||
| border: { | |||
| bottom: { style: 'thin', color: { auto: 1 } } | |||
| } | |||
| }; | |||
| } | |||
| } | |||
| // Calculate the maximum length of content in each column and set column width | |||
| const colWidths: number[] = []; | |||
| // Start with a base width for each column (optional, but can help with columns that have no data) | |||
| const maxCol = worksheet['!ref'] ? worksheet['!ref'].split(':')[1].charCodeAt(0) - 'A'.charCodeAt(0) + 1 : 0; | |||
| for (let col = 0; col < maxCol; col++) { | |||
| colWidths[col] = 10; // Default base width | |||
| } | |||
| const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: "", blankrows: true }) as (string | number)[][]; | |||
| // Skip the first row in the jsonData | |||
| for (let row = 1; row < jsonData.length; row++) { | |||
| jsonData[row].forEach((cell, index) => { | |||
| // Only process if the cell is not null/undefined | |||
| if (cell) { | |||
| const valueLength = cell.toString().length; | |||
| colWidths[index] = Math.max(colWidths[index] || 0, valueLength); | |||
| } | |||
| }); | |||
| } | |||
| // Apply calculated widths to each column, skipping the first row | |||
| worksheet['!cols'] = colWidths.map((width, index) => { | |||
| return { wch: width + 2 }; // +2 for a little extra padding | |||
| }); | |||
| // Format filename with date | |||
| const today = new Date().toISOString().split('T')[0].replace(/-/g, '_'); // Get current date and format as YYYY_MM_DD | |||
| const filename = `AR08_Project_P&L_Report_${today}.xlsx`; // Append formatted date to the filename | |||
| // Convert workbook back to XLSX file | |||
| XLSX.writeFile(workbook, filename); | |||
| } else { | |||
| throw new Error('Failed to load file'); | |||
| } | |||
| }; | |||
| reader.readAsArrayBuffer(data); | |||
| } catch (error) { | |||
| console.error('Error downloading the file: ', error); | |||
| } | |||
| //setIsLoading(false); | |||
| }; | |||
| return ( | |||
| <Card> | |||
| <CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}> | |||
| <Typography variant="overline">{t("Search Criteria")}</Typography> | |||
| <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | |||
| {criteria.map((c) => { | |||
| return ( | |||
| <Grid key={c.paramName} item xs={6}> | |||
| {c.type === "text" && ( | |||
| <TextField | |||
| label={c.label} | |||
| fullWidth | |||
| onChange={makeInputChangeHandler(c.paramName)} | |||
| value={inputs[c.paramName]} | |||
| /> | |||
| )} | |||
| {c.type === "select" && ( | |||
| <FormControl fullWidth> | |||
| <InputLabel>{c.label}</InputLabel> | |||
| <Select | |||
| label={c.label} | |||
| onChange={makeSelectChangeHandler(c.paramName)} | |||
| value={inputs[c.paramName]} | |||
| > | |||
| <MenuItem value={"All"}>{t("All")}</MenuItem> | |||
| {c.options.map((option, index) => ( | |||
| <MenuItem key={`${option}-${index}`} value={option}> | |||
| {option} | |||
| </MenuItem> | |||
| ))} | |||
| </Select> | |||
| </FormControl> | |||
| )} | |||
| {c.type === "dateRange" && ( | |||
| <LocalizationProvider | |||
| dateAdapter={AdapterDayjs} | |||
| // TODO: Should maybe use a custom adapterLocale here to support YYYY-MM-DD | |||
| adapterLocale="zh-hk" | |||
| > | |||
| <Box display="flex"> | |||
| <FormControl fullWidth> | |||
| <DatePicker | |||
| label={c.label} | |||
| onChange={makeDateChangeHandler(c.paramName)} | |||
| value={inputs[c.paramName] ? dayjs(inputs[c.paramName]) : null} | |||
| /> | |||
| </FormControl> | |||
| <Box | |||
| display="flex" | |||
| alignItems="center" | |||
| justifyContent="center" | |||
| marginInline={2} | |||
| > | |||
| {"-"} | |||
| </Box> | |||
| <FormControl fullWidth> | |||
| <DatePicker | |||
| label={c.label2} | |||
| onChange={makeDateToChangeHandler(c.paramName)} | |||
| value={inputs[c.paramName.concat("To") as T] ? dayjs(inputs[c.paramName.concat("To") as T]) : null} | |||
| /> | |||
| </FormControl> | |||
| </Box> | |||
| </LocalizationProvider> | |||
| )} | |||
| </Grid> | |||
| ); | |||
| })} | |||
| </Grid> | |||
| <CardActions sx={{ justifyContent: "flex-end" }}> | |||
| <Button | |||
| variant="text" | |||
| startIcon={<RestartAlt />} | |||
| onClick={handleReset} | |||
| > | |||
| {t("Reset")} | |||
| </Button> | |||
| <Button | |||
| variant="outlined" | |||
| startIcon={<Search />} | |||
| onClick={handleDownload} | |||
| > | |||
| {t("Download")} | |||
| </Button> | |||
| </CardActions> | |||
| </CardContent> | |||
| </Card> | |||
| ); | |||
| } | |||
| export default SearchBox; | |||
| @@ -0,0 +1,3 @@ | |||
| //src\components\SearchBox\index.ts | |||
| export { default } from "./SearchBox8"; | |||
| export type { Criterion } from "./SearchBox8"; | |||