@@ -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"; |