Compare commits
429 Commits
Kartik_Bug
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a2f105dd41 | |||
| 247bda1be4 | |||
| fc2d115f40 | |||
| 7f848eeb38 | |||
| b38eb1bc4b | |||
| 9cd9e0fbbe | |||
| 4238157fd4 | |||
| d7caf47498 | |||
| b4f1c48293 | |||
| a070d23304 | |||
| 32f16092db | |||
| 1372f9870a | |||
| d130ede851 | |||
| 9355efa4af | |||
| c438b51ef1 | |||
| 5b3f002772 | |||
| 0d9037dbdc | |||
| 55d455bb10 | |||
| 767e218987 | |||
| e0eec8ff69 | |||
| c38efe9934 | |||
| 9e5555ff9d | |||
| 8a18c0860e | |||
| e5b01e9e0f | |||
| b7268f9294 | |||
| 43ecdadfa9 | |||
| 005261abab | |||
| 9527427f47 | |||
| 10fc88c022 | |||
| 3b0097f46b | |||
| ecd726bbb1 | |||
| d14fb130c6 | |||
| b6ef3e0d22 | |||
| b3f489fe9f | |||
| edae70f8af | |||
| 4563e328a5 | |||
| 8f0ca4a9ca | |||
| dec959c495 | |||
| cb7e044b27 | |||
| 4c059afb72 | |||
| 97525e3cb2 | |||
| 2a0f7794b5 | |||
| 798ea24088 | |||
| 8460460caf | |||
| c8273070ac | |||
| dc4e48ad3b | |||
| 20b508bebc | |||
| 05c01d1d34 | |||
| 073897156e | |||
| 3a2fcf71ee | |||
| f0c6aea55d | |||
| 278a5651fe | |||
| 6fc3e674e5 | |||
| 9f4d82eb06 | |||
| 4ba0c823c0 | |||
| acf6a28191 | |||
| 98c90f2a9b | |||
| 6e89fbd680 | |||
| cc2a82e3f0 | |||
| b23518f796 | |||
| 9648d1a98b | |||
| aa947b791b | |||
| bd6332fa61 | |||
| 51cca64dd5 | |||
| 57d65a5fe7 | |||
| ca88928850 | |||
| 962286a4da | |||
| 0b02531909 | |||
| dec15278fa | |||
| 76df08e921 | |||
| 0052fed1e6 | |||
| da56c59ac9 | |||
| d9392c244e | |||
| a7f1ba97c3 | |||
| b80af5467c | |||
| e2035e1fd8 | |||
| 7176a86913 | |||
| a26e4d1dc2 | |||
| f91c7d6da1 | |||
| 272645f0b4 | |||
| 301684a12b | |||
| 9288ac1fbc | |||
| 376a2a397f | |||
| 58c2fbdf1b | |||
| 9592108472 | |||
| 3b032b7b07 | |||
| b8891d403f | |||
| 01568db61c | |||
| 80a974e3be | |||
| f3e05a11d6 | |||
| 222e6495a8 | |||
| 18a3b8a85b | |||
| d75296ffe8 | |||
| 6649cab6a2 | |||
| eab23389ed | |||
| 12b632f087 | |||
| 6ee4fb6d04 | |||
| 67bb685d4b | |||
| 8fd4e7f3f1 | |||
| aca2decb00 | |||
| f7f4b68997 | |||
| f839613066 | |||
| b58bd33774 | |||
| 136bc94c5b | |||
| 281a956ac8 | |||
| 31882c3d12 | |||
| a64635cd37 | |||
| 9b8c8c34ab | |||
| 704ba79289 | |||
| 3440467107 | |||
| f4edcfd2f3 | |||
| 62e5c6899a | |||
| 00a23b3de9 | |||
| 79161a8ede | |||
| 860779d096 | |||
| a2067e150d | |||
| edce5ef614 | |||
| dd944b3414 | |||
| 13d3572cf6 | |||
| 0dd7c19457 | |||
| 3cc4f0b416 | |||
| f8095ac9bf | |||
| 6280abf95e | |||
| 1200937097 | |||
| 5d1ccb9572 | |||
| 91ffc5a0e0 | |||
| 1c4804fed2 | |||
| 0e5e716df2 | |||
| aecaee7116 | |||
| 97dca1a10b | |||
| c35eacca5a | |||
| 5e27ed36fa | |||
| 8bcfcc5718 | |||
| ad1bef4f7b | |||
| 9886fac03e | |||
| a28a7fb444 | |||
| 470421b730 | |||
| 7505b790a7 | |||
| 8fd13247c7 | |||
| 0fec257354 | |||
| 638c033705 | |||
| 7872e21477 | |||
| 6928bbd309 | |||
| 3693af3d00 | |||
| d5df200ede | |||
| 9c6450496e | |||
| cfd3986479 | |||
| 764b145ad9 | |||
| 20c7cf7f37 | |||
| 6d74940c0c | |||
| 02dcd8611f | |||
| 02600308e8 | |||
| d1c72291a3 | |||
| fdbd81c5e7 | |||
| 28d5ef653d | |||
| 550b142d74 | |||
| 68335f0695 | |||
| 4ea20981fc | |||
| 965e1e4808 | |||
| 1c376fe91f | |||
| 61835cb189 | |||
| 375c482b61 | |||
| 72424eee53 | |||
| 22514b1fa0 | |||
| 61b209a082 | |||
| 198e31290c | |||
| 482f8a9bcb | |||
| 2489095b0b | |||
| eb8d269662 | |||
| bbd8ed12f6 | |||
| 1e7b4ba21e | |||
| 4de3987a37 | |||
| 7455d8a221 | |||
| 3f4b7d08d4 | |||
| 7ac3268514 | |||
| f8740472de | |||
| be72ca9a58 | |||
| c71c00c0f7 | |||
| 112e0ff798 | |||
| 96eb030457 | |||
| a873ace109 | |||
| fb164bd2f2 | |||
| 0d6708619f | |||
| edba191a2e | |||
| ddfe09b570 | |||
| acb899dd2e | |||
| ae66cb3705 | |||
| d52fa00de0 | |||
| ca8a41bb63 | |||
| 265c74f079 | |||
| 2ef1fcfd1d | |||
| 3233043cf2 | |||
| 3fddb686d3 | |||
| 4fd6e5cc1a | |||
| 1fb8eb9ef1 | |||
| 1a3890e837 | |||
| 2dbf08e330 | |||
| b044b88c49 | |||
| db815ba038 | |||
| 53fa013c39 | |||
| 22a1ad45e7 | |||
| 91dcd7c132 | |||
| 7d18edfa9b | |||
| fb08e48edd | |||
| 69c225ac72 | |||
| 182280e91d | |||
| 38bd8d36c0 | |||
| a4bccc9bf6 | |||
| 0e6dd93260 | |||
| 245219ad71 | |||
| 978a497f28 | |||
| 451d0a785f | |||
| 84f9cb2e29 | |||
| 9d9ca28bad | |||
| 1d218056ac | |||
| 58837cef0c | |||
| 1cd3bf6c7f | |||
| d2b10495bd | |||
| ae9c4833b3 | |||
| e69efe61cb | |||
| 92b1531b75 | |||
| 57edd92dce | |||
| 49eaf857ad | |||
| ccdfc193c6 | |||
| 817b31379e | |||
| b36120f73d | |||
| e2ae2e5fbd | |||
| 521e6690cb | |||
| 1f4a7e5e9c | |||
| 1d3fcff859 | |||
| 1286184e1f | |||
| 4e315aafcf | |||
| 52e12426af | |||
| d975664023 | |||
| d87dae4799 | |||
| 4683eff749 | |||
| 772a3e2829 | |||
| ca7b0cda13 | |||
| a380a7ab29 | |||
| c609387924 | |||
| 69cc3b9383 | |||
| 6ebbc853bc | |||
| 71dd35adc2 | |||
| 533b40d1bf | |||
| 42a80bbd68 | |||
| 397020ccb4 | |||
| 71932ea6dc | |||
| 693cabf63d | |||
| 4afe43d116 | |||
| b9b3788dda | |||
| 5e1ccc9b05 | |||
| aee510f527 | |||
| 7e6020e3db | |||
| cf78d17cf5 | |||
| 72dbdb0fe0 | |||
| afcd1934f9 | |||
|
|
3e5afe0bc6 | ||
|
|
070fa93fca | ||
| 53a9cbc30b | |||
| 70acf57266 | |||
| b2d7349fc9 | |||
| a0f7e5c57b | |||
| fa7dc2860c | |||
| a1a935b0d5 | |||
|
|
e610cc08c1 | ||
| 9711144236 | |||
| 1a88f5fec5 | |||
| ce73cfad21 | |||
| 7dafd4a45f | |||
| 2e3e3aa6ca | |||
| 158c934a9f | |||
| f6d864d42e | |||
| 90b96864be | |||
| 5a048f7066 | |||
| 731d2dbed7 | |||
| 25de45b31b | |||
| 58b5da1793 | |||
| 0746e5c349 | |||
| 83143dff0a | |||
| 994f22e8c0 | |||
| 84a5be52f8 | |||
| b39df5f665 | |||
| 9bdcc74486 | |||
| 005fdb3490 | |||
| 9223f7a176 | |||
| 27b62c858d | |||
| 00d6774e06 | |||
| 1ef82ad0b2 | |||
| 1da587d010 | |||
| 164b82e1c7 | |||
| e9d8b6daea | |||
| 9b37288901 | |||
| 54e609883d | |||
| 7d17422681 | |||
| d67121c150 | |||
| e154bac64a | |||
| ea350db98b | |||
| 133024bc5c | |||
| ea219b7176 | |||
| af5519fd60 | |||
| 08194dd8ef | |||
| 1452e77bc5 | |||
| 7fa2ca9227 | |||
| 0d9ef7f248 | |||
| 9a3488c92b | |||
| 1b144aab8a | |||
| 2e65007f26 | |||
| 3c4c25b449 | |||
| 979293ad90 | |||
| 99eaf92e3f | |||
| 28b0541894 | |||
| a48fc1d989 | |||
| 78a0ecebf1 | |||
| 8eb8e27f89 | |||
| 012a89b3ea | |||
| 18698a67e3 | |||
| 701d1adc0b | |||
| b2c68824dd | |||
| b3b7297bc3 | |||
| 9311f41f56 | |||
| 9ba2ecfb1f | |||
| a27b8571b5 | |||
| fd36298543 | |||
| 09bb58e50e | |||
| 01a5766074 | |||
| 0348f6da8e | |||
| 1c0e8655c4 | |||
| 6211f52e3a | |||
| df9107f0d8 | |||
| eea7252b96 | |||
| 442ecff926 | |||
| 0abd77dab7 | |||
| 4b0ea3a0db | |||
| d3218eb77a | |||
| 4ad87af7f4 | |||
| 7b15309dbf | |||
| 834ce62e67 | |||
| 889b477dd0 | |||
| 19f8189fc3 | |||
| 970c195ca5 | |||
| d944d3a389 | |||
| e0c7eee1fd | |||
| 6baa2896c2 | |||
| a835e75f66 | |||
| cc7ef47055 | |||
| 95fbac4760 | |||
| a86c815ca2 | |||
| daa1a29e8a | |||
| 794429821b | |||
| 7d94c17c71 | |||
| fb6a8255c9 | |||
| 7ef10e3e5b | |||
| 0a489be675 | |||
| c014d6c929 | |||
| 2c378745fa | |||
| 080d2307ca | |||
| d2288ea967 | |||
| 42086d7f3a | |||
| 020020056e | |||
| 9b3ffdb33d | |||
| d70c8e5995 | |||
| e6e90c7d4e | |||
| 19241cc556 | |||
| 7c2744058d | |||
| a963176c95 | |||
| 6bc8c65c81 | |||
| 45bd5a7f66 | |||
| e179a267aa | |||
| 9c51378963 | |||
| 4927680fe3 | |||
| 9af7a5ceb2 | |||
| 94eb283b2d | |||
| 0e3a634205 | |||
| 2ef56e7f83 | |||
| d80fa27906 | |||
| 70110192f4 | |||
| 548597ed4f | |||
| 75ca3d1504 | |||
| f20ff7eb73 | |||
| ce59869827 | |||
| d77e3c5f03 | |||
| 2f2ddb0576 | |||
| 2e14fc862d | |||
| 84500f6913 | |||
| 2790f50275 | |||
| 8c48b83581 | |||
| f992dbeaf1 | |||
| d1098e64a7 | |||
| 212e969258 | |||
| 9de1613bd9 | |||
| 9676d45710 | |||
| 7e5b6952f5 | |||
| 0484de498c | |||
| a7ccaa2812 | |||
| 5fb7e89cb2 | |||
| 2027bd3d17 | |||
| ee9698f665 | |||
| 25ec5a354c | |||
| 8335c42935 | |||
| 3963002a2d | |||
| 717264c9f9 | |||
| 1a04dd51fc | |||
| c83de16466 | |||
| b51b3db9ec | |||
| 79553cab4e | |||
| 7a74084841 | |||
| 7638f8bdaa | |||
| 78f304a490 | |||
| 0b38436dfe | |||
| 15581f3f26 | |||
| 8dc064340b | |||
| 285b853026 | |||
| 81e5456e1d | |||
| 4741025dcb | |||
| aec6bd64ff | |||
| d6de8cbfc1 | |||
| 44cc6c6e81 | |||
| 6604a4db13 | |||
| b66106c301 | |||
| 47d0e332f1 | |||
| e454917763 | |||
| b113675cc2 | |||
| 025b13ea64 | |||
| 2642532b60 | |||
| b91487712d | |||
| 6ca6ec31f7 | |||
| 1abed1de3a | |||
| 957f790fce | |||
| 6a472b39ef |
119
package-lock.json
generated
@ -809,9 +809,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/source-map": {
|
"node_modules/@jridgewell/source-map": {
|
||||||
"version": "0.3.6",
|
"version": "0.3.11",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
||||||
"integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
|
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -1552,13 +1552,13 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.13.13",
|
"version": "24.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz",
|
||||||
"integrity": "sha512-ClsL5nMwKaBRwPcCvH8E7+nU4GxHVx1axNvMZTFHMEfNI7oahimt26P5zjVCRrjiIWj6YFXfE1v3dEp94wLcGQ==",
|
"integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.20.0"
|
"undici-types": "~7.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/prop-types": {
|
"node_modules/@types/prop-types": {
|
||||||
@ -1835,9 +1835,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "8.14.0",
|
"version": "8.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||||
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@ -1845,6 +1846,19 @@
|
|||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/acorn-import-phases": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"acorn": "^8.14.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/acorn-jsx": {
|
"node_modules/acorn-jsx": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
|
||||||
@ -2625,9 +2639,9 @@
|
|||||||
"integrity": "sha512-ZpSAUOZ2Izby7qnZluSrAlGgGQzucmFbN0n64dYzocYxnxV5ufurpj3VgEe4cUp7ir9LmeLxNYo8bVnlM8bQHw=="
|
"integrity": "sha512-ZpSAUOZ2Izby7qnZluSrAlGgGQzucmFbN0n64dYzocYxnxV5ufurpj3VgEe4cUp7ir9LmeLxNYo8bVnlM8bQHw=="
|
||||||
},
|
},
|
||||||
"node_modules/enhanced-resolve": {
|
"node_modules/enhanced-resolve": {
|
||||||
"version": "5.18.1",
|
"version": "5.18.3",
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
|
||||||
"integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==",
|
"integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -2741,9 +2755,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/es-module-lexer": {
|
"node_modules/es-module-lexer": {
|
||||||
"version": "1.6.0",
|
"version": "1.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
|
||||||
"integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==",
|
"integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
@ -3138,9 +3152,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/fast-uri": {
|
"node_modules/fast-uri": {
|
||||||
"version": "3.0.6",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
|
||||||
"integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==",
|
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@ -5163,9 +5177,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/schema-utils": {
|
"node_modules/schema-utils": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
|
||||||
"integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==",
|
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -5567,24 +5581,28 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tapable": {
|
"node_modules/tapable": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz",
|
||||||
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
|
"integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/webpack"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/terser": {
|
"node_modules/terser": {
|
||||||
"version": "5.39.0",
|
"version": "5.44.0",
|
||||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz",
|
"resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz",
|
||||||
"integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==",
|
"integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==",
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/source-map": "^0.3.3",
|
"@jridgewell/source-map": "^0.3.3",
|
||||||
"acorn": "^8.8.2",
|
"acorn": "^8.15.0",
|
||||||
"commander": "^2.20.0",
|
"commander": "^2.20.0",
|
||||||
"source-map-support": "~0.5.20"
|
"source-map-support": "~0.5.20"
|
||||||
},
|
},
|
||||||
@ -5777,9 +5795,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "6.20.0",
|
"version": "7.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz",
|
||||||
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
|
"integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
@ -5907,9 +5925,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/watchpack": {
|
"node_modules/watchpack": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz",
|
||||||
"integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==",
|
"integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -5927,21 +5945,23 @@
|
|||||||
"license": "BSD-2-Clause"
|
"license": "BSD-2-Clause"
|
||||||
},
|
},
|
||||||
"node_modules/webpack": {
|
"node_modules/webpack": {
|
||||||
"version": "5.98.0",
|
"version": "5.101.3",
|
||||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz",
|
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz",
|
||||||
"integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==",
|
"integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/eslint-scope": "^3.7.7",
|
"@types/eslint-scope": "^3.7.7",
|
||||||
"@types/estree": "^1.0.6",
|
"@types/estree": "^1.0.8",
|
||||||
|
"@types/json-schema": "^7.0.15",
|
||||||
"@webassemblyjs/ast": "^1.14.1",
|
"@webassemblyjs/ast": "^1.14.1",
|
||||||
"@webassemblyjs/wasm-edit": "^1.14.1",
|
"@webassemblyjs/wasm-edit": "^1.14.1",
|
||||||
"@webassemblyjs/wasm-parser": "^1.14.1",
|
"@webassemblyjs/wasm-parser": "^1.14.1",
|
||||||
"acorn": "^8.14.0",
|
"acorn": "^8.15.0",
|
||||||
|
"acorn-import-phases": "^1.0.3",
|
||||||
"browserslist": "^4.24.0",
|
"browserslist": "^4.24.0",
|
||||||
"chrome-trace-event": "^1.0.2",
|
"chrome-trace-event": "^1.0.2",
|
||||||
"enhanced-resolve": "^5.17.1",
|
"enhanced-resolve": "^5.17.3",
|
||||||
"es-module-lexer": "^1.2.1",
|
"es-module-lexer": "^1.2.1",
|
||||||
"eslint-scope": "5.1.1",
|
"eslint-scope": "5.1.1",
|
||||||
"events": "^3.2.0",
|
"events": "^3.2.0",
|
||||||
@ -5951,11 +5971,11 @@
|
|||||||
"loader-runner": "^4.2.0",
|
"loader-runner": "^4.2.0",
|
||||||
"mime-types": "^2.1.27",
|
"mime-types": "^2.1.27",
|
||||||
"neo-async": "^2.6.2",
|
"neo-async": "^2.6.2",
|
||||||
"schema-utils": "^4.3.0",
|
"schema-utils": "^4.3.2",
|
||||||
"tapable": "^2.1.1",
|
"tapable": "^2.1.1",
|
||||||
"terser-webpack-plugin": "^5.3.11",
|
"terser-webpack-plugin": "^5.3.11",
|
||||||
"watchpack": "^2.4.1",
|
"watchpack": "^2.4.1",
|
||||||
"webpack-sources": "^3.2.3"
|
"webpack-sources": "^3.3.3"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"webpack": "bin/webpack.js"
|
"webpack": "bin/webpack.js"
|
||||||
@ -5974,15 +5994,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/webpack-sources": {
|
"node_modules/webpack-sources": {
|
||||||
"version": "3.2.3",
|
"version": "3.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz",
|
||||||
"integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
|
"integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.13.0"
|
"node": ">=10.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/webpack/node_modules/@types/estree": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/webpack/node_modules/eslint-scope": {
|
"node_modules/webpack/node_modules/eslint-scope": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||||
|
|||||||
@ -1,8 +1,306 @@
|
|||||||
:root,
|
:root,
|
||||||
[data-bs-theme="light"] {
|
[data-bs-theme="light"] {
|
||||||
--bs-nav-link-font-size: 0.7375rem;
|
--bs-nav-link-font-size: 0.7375rem;
|
||||||
|
--bg-border-color :#f8f6f6
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
padding: 0.5rem var(--bs-card-cap-padding-x);
|
padding: 0.5rem var(--bs-card-cap-padding-x);
|
||||||
}
|
}
|
||||||
|
.table_header_border {
|
||||||
|
border-bottom:2px solid var(--bs-table-border-color) ;
|
||||||
|
}
|
||||||
|
.text-gary-80 {
|
||||||
|
color:var(--bs-gray-500)
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-royalblue{
|
||||||
|
color: #1796e3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-md {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-md-b {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
.cursor-wait{
|
||||||
|
cursor:wait;
|
||||||
|
}
|
||||||
|
.cursor-notallowed {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-xxs { font-size: 0.55rem; } /* 8px */
|
||||||
|
.text-xs { font-size: 0.75rem; } /* 12px */
|
||||||
|
.text-sm { font-size: 0.875rem; } /* 14px */
|
||||||
|
.text-base { font-size: 1rem; } /* 16px */
|
||||||
|
.text-lg { font-size: 1.125rem; } /* 18px */
|
||||||
|
.text-xl { font-size: 1.25rem; } /* 20px */
|
||||||
|
.text-2xl { font-size: 1.5rem; } /* 24px */
|
||||||
|
.text-3xl { font-size: 1.875rem; } /* 30px */
|
||||||
|
.text-4xl { font-size: 2.25rem; } /* 36px */
|
||||||
|
.text-5xl { font-size: 3rem; } /* 48px */
|
||||||
|
.text-6xl { font-size: 3.75rem; } /* 60px */
|
||||||
|
.text-7xl { font-size: 4.5rem; } /* 72px */
|
||||||
|
.text-8xl { font-size: 6rem; } /* 96px */
|
||||||
|
.text-9xl { font-size: 8rem; } /* 128px */
|
||||||
|
|
||||||
|
|
||||||
|
/* */
|
||||||
|
|
||||||
|
.w-0 { width: 0px; }
|
||||||
|
.w-px { width: 1px; }
|
||||||
|
.w-1 { width: 0.25rem; } /* 4px */
|
||||||
|
.w-2 { width: 0.5rem; } /* 8px */
|
||||||
|
.w-3 { width: 0.75rem; } /* 12px */
|
||||||
|
.w-4 { width: 1rem; } /* 16px */
|
||||||
|
.w-5 { width: 1.25rem; } /* 20px */
|
||||||
|
.w-6 { width: 1.5rem; } /* 24px */
|
||||||
|
.w-8 { width: 2rem; } /* 32px */
|
||||||
|
.w-10 { width: 2.5rem; } /* 40px */
|
||||||
|
.w-12 { width: 3rem; } /* 48px */
|
||||||
|
.w-16 { width: 4rem; } /* 64px */
|
||||||
|
.w-20 { width: 5rem; } /* 80px */
|
||||||
|
.w-24 { width: 6rem; } /* 96px */
|
||||||
|
.w-32 { width: 8rem; } /* 128px */
|
||||||
|
.w-40 { width: 10rem; } /* 160px */
|
||||||
|
.w-48 { width: 12rem; } /* 192px */
|
||||||
|
.w-56 { width: 14rem; } /* 224px */
|
||||||
|
.w-64 { width: 16rem; } /* 256px */
|
||||||
|
.w-auto { width: auto; }
|
||||||
|
.w-full { width: 100%; }
|
||||||
|
.w-screen{ width: 100vw; }
|
||||||
|
.w-min { width: min-content; }
|
||||||
|
.w-max { width: max-content; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.h-0 { height: 0px; }
|
||||||
|
.h-px { height: 1px; }
|
||||||
|
.h-1 { height: 0.25rem; } /* 4px */
|
||||||
|
.h-2 { height: 0.5rem; } /* 8px */
|
||||||
|
.h-3 { height: 0.75rem; } /* 12px */
|
||||||
|
.h-4 { height: 1rem; } /* 16px */
|
||||||
|
.h-5 { height: 1.25rem; } /* 20px */
|
||||||
|
.h-6 { height: 1.5rem; } /* 24px */
|
||||||
|
.h-8 { height: 2rem; } /* 32px */
|
||||||
|
.h-10 { height: 2.5rem; } /* 40px */
|
||||||
|
.h-12 { height: 3rem; } /* 48px */
|
||||||
|
.h-16 { height: 4rem; } /* 64px */
|
||||||
|
.h-20 { height: 5rem; } /* 80px */
|
||||||
|
.h-24 { height: 6rem; } /* 96px */
|
||||||
|
.h-32 { height: 8rem; } /* 128px */
|
||||||
|
.h-40 { height: 10rem; } /* 160px */
|
||||||
|
.h-48 { height: 12rem; } /* 192px */
|
||||||
|
.h-56 { height: 14rem; } /* 224px */
|
||||||
|
.h-64 { height: 16rem; } /* 256px */
|
||||||
|
.h-auto { height: auto; }
|
||||||
|
.h-full { height: 100%; }
|
||||||
|
.h-screen{ height: 100vh; }
|
||||||
|
.h-min { height: min-content; }
|
||||||
|
.h-max { height: max-content; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ==========================
|
||||||
|
Base Font Sizes (mobile first)
|
||||||
|
========================== */
|
||||||
|
.text-xxs { font-size: 0.55rem; } /* 8px */
|
||||||
|
.text-xs { font-size: 0.75rem; } /* 12px */
|
||||||
|
.text-sm { font-size: 0.875rem; } /* 14px */
|
||||||
|
.text-base{ font-size: 1rem; } /* 16px */
|
||||||
|
.text-lg { font-size: 1.125rem; } /* 18px */
|
||||||
|
.text-xl { font-size: 1.25rem; } /* 20px */
|
||||||
|
.text-2xl { font-size: 1.5rem; } /* 24px */
|
||||||
|
.text-3xl { font-size: 1.875rem; } /* 30px */
|
||||||
|
.text-4xl { font-size: 2.25rem; } /* 36px */
|
||||||
|
.text-5xl { font-size: 3rem; } /* 48px */
|
||||||
|
.text-6xl { font-size: 3.75rem; } /* 60px */
|
||||||
|
.text-7xl { font-size: 4.5rem; } /* 72px */
|
||||||
|
.text-8xl { font-size: 6rem; } /* 96px */
|
||||||
|
.text-9xl { font-size: 8rem; } /* 128px */
|
||||||
|
|
||||||
|
/* ==========================
|
||||||
|
Base Heights
|
||||||
|
========================== */
|
||||||
|
.h-0 { height: 0; }
|
||||||
|
.h-px { height: 1px; }
|
||||||
|
.h-1 { height: 0.25rem; } /* 4px */
|
||||||
|
.h-2 { height: 0.5rem; } /* 8px */
|
||||||
|
.h-3 { height: 0.75rem; } /* 12px */
|
||||||
|
.h-4 { height: 1rem; } /* 16px */
|
||||||
|
.h-5 { height: 1.25rem; } /* 20px */
|
||||||
|
.h-6 { height: 1.5rem; } /* 24px */
|
||||||
|
.h-8 { height: 2rem; } /* 32px */
|
||||||
|
.h-10 { height: 2.5rem; } /* 40px */
|
||||||
|
.h-12 { height: 3rem; } /* 48px */
|
||||||
|
.h-16 { height: 4rem; } /* 64px */
|
||||||
|
.h-20 { height: 5rem; } /* 80px */
|
||||||
|
.h-24 { height: 6rem; } /* 96px */
|
||||||
|
.h-32 { height: 8rem; } /* 128px */
|
||||||
|
.h-40 { height: 10rem; } /* 160px */
|
||||||
|
.h-48 { height: 12rem; } /* 192px */
|
||||||
|
.h-56 { height: 14rem; } /* 224px */
|
||||||
|
.h-64 { height: 16rem; } /* 256px */
|
||||||
|
.h-full { height: 100%; }
|
||||||
|
.h-screen{ height: 100vh; }
|
||||||
|
|
||||||
|
/* ==========================
|
||||||
|
Base Widths
|
||||||
|
========================== */
|
||||||
|
.w-0 { width: 0; }
|
||||||
|
.w-px { width: 1px; }
|
||||||
|
.w-1 { width: 0.25rem; }
|
||||||
|
.w-2 { width: 0.5rem; }
|
||||||
|
.w-3 { width: 0.75rem; }
|
||||||
|
.w-4 { width: 1rem; }
|
||||||
|
.w-5 { width: 1.25rem; }
|
||||||
|
.w-6 { width: 1.5rem; }
|
||||||
|
.w-8 { width: 2rem; }
|
||||||
|
.w-10 { width: 2.5rem; }
|
||||||
|
.w-12 { width: 3rem; }
|
||||||
|
.w-16 { width: 4rem; }
|
||||||
|
.w-20 { width: 5rem; }
|
||||||
|
.w-24 { width: 6rem; }
|
||||||
|
.w-32 { width: 8rem; }
|
||||||
|
.w-40 { width: 10rem; }
|
||||||
|
.w-48 { width: 12rem; }
|
||||||
|
.w-56 { width: 14rem; }
|
||||||
|
.w-64 { width: 16rem; }
|
||||||
|
.w-full { width: 100%; }
|
||||||
|
.w-screen{ width: 100vw; }
|
||||||
|
|
||||||
|
/* ==========================
|
||||||
|
Responsive Variants
|
||||||
|
========================== */
|
||||||
|
@media (min-width: 576px) { /* sm */
|
||||||
|
/* Font */
|
||||||
|
.text-xxs-sm { font-size: 0.55rem; }
|
||||||
|
.text-xs-sm { font-size: 0.75rem; }
|
||||||
|
.text-sm-sm { font-size: 0.875rem; }
|
||||||
|
.text-base-sm{ font-size: 1rem; }
|
||||||
|
.text-lg-sm { font-size: 1.125rem; }
|
||||||
|
.text-xl-sm { font-size: 1.25rem; }
|
||||||
|
.text-2xl-sm{ font-size: 1.5rem; }
|
||||||
|
|
||||||
|
/* Height */
|
||||||
|
.h-1-sm{ height: 0.25rem; }
|
||||||
|
.h-2-sm{ height: 0.5rem; }
|
||||||
|
.h-3-sm{ height: 0.75rem; }
|
||||||
|
.h-4-sm{ height: 1rem; }
|
||||||
|
.h-5-sm{ height: 1.25rem; }
|
||||||
|
.h-6-sm{ height: 1.5rem; }
|
||||||
|
.h-8-sm{ height: 2rem; }
|
||||||
|
.h-10-sm{ height: 2.5rem; }
|
||||||
|
|
||||||
|
/* Width */
|
||||||
|
.w-1-sm{ width: 0.25rem; }
|
||||||
|
.w-2-sm{ width: 0.5rem; }
|
||||||
|
.w-3-sm{ width: 0.75rem; }
|
||||||
|
.w-4-sm{ width: 1rem; }
|
||||||
|
.w-5-sm{ width: 1.25rem; }
|
||||||
|
.w-6-sm{ width: 1.5rem; }
|
||||||
|
.w-8-sm{ width: 2rem; }
|
||||||
|
.w-10-sm{ width: 2.5rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) { /* md */
|
||||||
|
/* Font */
|
||||||
|
.text-xxs-md { font-size: 0.55rem; }
|
||||||
|
.text-xs-md { font-size: 0.75rem; }
|
||||||
|
.text-sm-md { font-size: 0.875rem; }
|
||||||
|
.text-base-md{ font-size: 1rem; }
|
||||||
|
.text-lg-md { font-size: 1.125rem; }
|
||||||
|
.text-xl-md { font-size: 1.25rem; }
|
||||||
|
.text-2xl-md{ font-size: 1.5rem; }
|
||||||
|
|
||||||
|
/* Height */
|
||||||
|
.h-1-md{ height: 0.25rem; }
|
||||||
|
.h-2-md{ height: 0.5rem; }
|
||||||
|
.h-3-md{ height: 0.75rem; }
|
||||||
|
.h-4-md{ height: 1rem; }
|
||||||
|
.h-5-md{ height: 1.25rem; }
|
||||||
|
.h-6-md{ height: 1.5rem; }
|
||||||
|
.h-8-md{ height: 2rem; }
|
||||||
|
.h-10-md{ height: 2.5rem; }
|
||||||
|
|
||||||
|
/* Width */
|
||||||
|
.w-1-md{ width: 0.25rem; }
|
||||||
|
.w-2-md{ width: 0.5rem; }
|
||||||
|
.w-3-md{ width: 0.75rem; }
|
||||||
|
.w-4-md{ width: 1rem; }
|
||||||
|
.w-5-md{ width: 1.25rem; }
|
||||||
|
.w-6-md{ width: 1.5rem; }
|
||||||
|
.w-8-md{ width: 2rem; }
|
||||||
|
.w-10-md{ width: 2.5rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 992px) { /* lg */
|
||||||
|
/* Font */
|
||||||
|
.text-xxs-lg { font-size: 0.55rem; }
|
||||||
|
.text-xs-lg { font-size: 0.75rem; }
|
||||||
|
.text-sm-lg { font-size: 0.875rem; }
|
||||||
|
.text-base-lg{ font-size: 1rem; }
|
||||||
|
.text-lg-lg { font-size: 1.125rem; }
|
||||||
|
.text-xl-lg { font-size: 1.25rem; }
|
||||||
|
.text-2xl-lg{ font-size: 1.5rem; }
|
||||||
|
|
||||||
|
/* Height */
|
||||||
|
.h-1-lg{ height: 0.25rem; }
|
||||||
|
.h-2-lg{ height: 0.5rem; }
|
||||||
|
.h-3-lg{ height: 0.75rem; }
|
||||||
|
.h-4-lg{ height: 1rem; }
|
||||||
|
.h-5-lg{ height: 1.25rem; }
|
||||||
|
.h-6-lg{ height: 1.5rem; }
|
||||||
|
.h-8-lg{ height: 2rem; }
|
||||||
|
.h-10-lg{ height: 2.5rem; }
|
||||||
|
|
||||||
|
/* Width */
|
||||||
|
.w-1-lg{ width: 0.25rem; }
|
||||||
|
.w-2-lg{ width: 0.5rem; }
|
||||||
|
.w-3-lg{ width: 0.75rem; }
|
||||||
|
.w-4-lg{ width: 1rem; }
|
||||||
|
.w-5-lg{ width: 1.25rem; }
|
||||||
|
.w-6-lg{ width: 1.5rem; }
|
||||||
|
.w-8-lg{ width: 2rem; }
|
||||||
|
.w-10-lg{ width: 2.5rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1200px) { /* xl */
|
||||||
|
/* Font */
|
||||||
|
.text-xxs-xl { font-size: 0.55rem; }
|
||||||
|
.text-xs-xl { font-size: 0.75rem; }
|
||||||
|
.text-sm-xl { font-size: 0.875rem; }
|
||||||
|
.text-base-xl{ font-size: 1rem; }
|
||||||
|
.text-lg-xl { font-size: 1.125rem; }
|
||||||
|
.text-xl-xl { font-size: 1.25rem; }
|
||||||
|
.text-2xl-xl{ font-size: 1.5rem; }
|
||||||
|
|
||||||
|
/* Height */
|
||||||
|
.h-1-xl{ height: 0.25rem; }
|
||||||
|
.h-2-xl{ height: 0.5rem; }
|
||||||
|
.h-3-xl{ height: 0.75rem; }
|
||||||
|
.h-4-xl{ height: 1rem; }
|
||||||
|
.h-5-xl{ height: 1.25rem; }
|
||||||
|
.h-6-xl{ height: 1.5rem; }
|
||||||
|
.h-8-xl{ height: 2rem; }
|
||||||
|
.h-10-xl{ height: 2.5rem; }
|
||||||
|
|
||||||
|
/* Width */
|
||||||
|
.w-1-xl{ width: 0.25rem; }
|
||||||
|
.w-2-xl{ width: 0.5rem; }
|
||||||
|
.w-3-xl{ width: 0.75rem; }
|
||||||
|
.w-4-xl{ width: 1rem; }
|
||||||
|
.w-5-xl{ width: 1.25rem; }
|
||||||
|
.w-6-xl{ width: 1.5rem; }
|
||||||
|
.w-8-xl{ width: 2rem; }
|
||||||
|
.w-10-xl{ width: 2.5rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.cursor-not-allowed{
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|||||||
5
public/assets/img/SP-Placeholdeer.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="120" height="120" fill="#EFF1F3"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.2503 38.4816C33.2603 37.0472 34.4199 35.8864 35.8543 35.875H83.1463C84.5848 35.875 85.7503 37.0431 85.7503 38.4816V80.5184C85.7403 81.9528 84.5807 83.1136 83.1463 83.125H35.8543C34.4158 83.1236 33.2503 81.957 33.2503 80.5184V38.4816ZM80.5006 41.1251H38.5006V77.8751L62.8921 53.4783C63.9172 52.4536 65.5788 52.4536 66.6039 53.4783L80.5006 67.4013V41.1251ZM43.75 51.6249C43.75 54.5244 46.1005 56.8749 49 56.8749C51.8995 56.8749 54.25 54.5244 54.25 51.6249C54.25 48.7254 51.8995 46.3749 49 46.3749C46.1005 46.3749 43.75 48.7254 43.75 51.6249Z" fill="#687787"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 888 B |
BIN
public/assets/img/orglogo.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
4
public/assets/vendor/css/core.css
vendored
@ -18613,6 +18613,10 @@ li:not(:first-child) .dropdown-item,
|
|||||||
min-height: 70vh !important;
|
min-height: 70vh !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-min-h{
|
||||||
|
min-height: 60vh !important;
|
||||||
|
}
|
||||||
|
|
||||||
.flex-fill {
|
.flex-fill {
|
||||||
flex: 1 1 auto !important;
|
flex: 1 1 auto !important;
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 222 KiB After Width: | Height: | Size: 222 KiB |
|
Before Width: | Height: | Size: 308 KiB After Width: | Height: | Size: 308 KiB |
|
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 165 KiB |
|
Before Width: | Height: | Size: 217 KiB After Width: | Height: | Size: 217 KiB |
|
Before Width: | Height: | Size: 278 KiB After Width: | Height: | Size: 278 KiB |
|
Before Width: | Height: | Size: 202 KiB After Width: | Height: | Size: 202 KiB |
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 411 KiB After Width: | Height: | Size: 411 KiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 787 KiB After Width: | Height: | Size: 787 KiB |
|
Before Width: | Height: | Size: 786 KiB After Width: | Height: | Size: 786 KiB |
|
Before Width: | Height: | Size: 237 KiB After Width: | Height: | Size: 237 KiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 192 KiB |
|
Before Width: | Height: | Size: 232 KiB |
|
Before Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 184 KiB |
|
Before Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 295 KiB |
|
Before Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 401 B After Width: | Height: | Size: 401 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
1
public/img/icons/inventory.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg idth="64" height="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path opacity="0.2" fill-rule="evenodd" clip-rule="evenodd" d="M0 142.1L0 480c0 17.7 14.3 32 32 32s32-14.3 32-32l0-240c0-17.7 14.3-32 32-32l384 0c17.7 0 32 14.3 32 32l0 240c0 17.7 14.3 32 32 32s32-14.3 32-32l0-337.9c0-27.5-17.6-52-43.8-60.7L303.2 5.1c-9.9-3.3-20.5-3.3-30.4 0L43.8 81.4C17.6 90.1 0 114.6 0 142.1zM464 256l-352 0 0 64 352 0 0-64zM112 416l352 0 0-64-352 0 0 64zm352 32l-352 0 0 64 352 0 0-64z"/></svg>
|
||||||
|
After Width: | Height: | Size: 500 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 860 B After Width: | Height: | Size: 860 B |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 224 KiB |
|
Before Width: | Height: | Size: 860 KiB After Width: | Height: | Size: 860 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
@ -4,6 +4,7 @@ import { ToastContainer } from "react-toastify";
|
|||||||
import { QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||||
import { queryClient } from "./layouts/AuthLayout";
|
import { queryClient } from "./layouts/AuthLayout";
|
||||||
|
import ModalProvider from "./ModalProvider";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -11,6 +12,7 @@ const App = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="app">
|
<div className="app">
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<ModalProvider/>
|
||||||
<DireProvider>
|
<DireProvider>
|
||||||
<AppRoutes />
|
<AppRoutes />
|
||||||
</DireProvider>
|
</DireProvider>
|
||||||
|
|||||||
@ -1,112 +0,0 @@
|
|||||||
import React, {
|
|
||||||
createContext,
|
|
||||||
useContext,
|
|
||||||
useState,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
} from "react";
|
|
||||||
|
|
||||||
const ModalContext = createContext();
|
|
||||||
|
|
||||||
export const useModal = () => useContext(ModalContext);
|
|
||||||
|
|
||||||
// ModalProvider to manage modal state and expose functionality to the rest of the app
|
|
||||||
export const ModalProvider = ({ children }) => {
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const [modalContent, setModalContent] = useState(null);
|
|
||||||
const [onSubmit, setOnSubmit] = useState(null);
|
|
||||||
const [modalSize, setModalSize] = useState("lg");
|
|
||||||
|
|
||||||
// Ref to track the modal content element
|
|
||||||
const modalRef = useRef(null);
|
|
||||||
|
|
||||||
const openModal = (content, onSubmitCallback, size = "lg") => {
|
|
||||||
setModalContent(content); // Set modal content dynamically
|
|
||||||
setOnSubmit(() => onSubmitCallback); // Set the submit handler dynamically
|
|
||||||
setIsOpen(true); // Open the modal
|
|
||||||
setModalSize(size); // Set the modal size
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function to close the modal
|
|
||||||
const closeModal = () => {
|
|
||||||
setIsOpen(false); // Close the modal
|
|
||||||
setModalContent(null); // Clear modal content
|
|
||||||
setOnSubmit(null); // Clear the submit callback
|
|
||||||
setModalSize("lg"); // Reset modal size
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleEscape = (event) => {
|
|
||||||
if (event.key === "Escape") {
|
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
document.addEventListener("keydown", handleEscape);
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener("keydown", handleEscape);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleClickOutside = (event) => {
|
|
||||||
if (modalRef.current && !modalRef.current.contains(event.target)) {
|
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
document.addEventListener("mousedown", handleClickOutside);
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener("mousedown", handleClickOutside);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ModalContext.Provider
|
|
||||||
value={{
|
|
||||||
isOpen,
|
|
||||||
openModal,
|
|
||||||
closeModal,
|
|
||||||
modalContent,
|
|
||||||
modalSize,
|
|
||||||
onSubmit,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
|
|
||||||
{isOpen && (
|
|
||||||
<div style={overlayStyles}>
|
|
||||||
<div
|
|
||||||
ref={modalRef}
|
|
||||||
style={{
|
|
||||||
...modalStyles,
|
|
||||||
maxWidth: modalSize === "sm" ? "400px" : "800px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div>{modalContent}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</ModalContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const overlayStyles = {
|
|
||||||
position: "fixed",
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
zIndex: 1050,
|
|
||||||
};
|
|
||||||
|
|
||||||
const modalStyles = {
|
|
||||||
backgroundColor: "white",
|
|
||||||
padding: "20px",
|
|
||||||
borderRadius: "5px",
|
|
||||||
boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)",
|
|
||||||
width: "90%",
|
|
||||||
maxWidth: "800px",
|
|
||||||
};
|
|
||||||
25
src/ModalProvider.jsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { useOrganizationModal } from "./hooks/useOrganization";
|
||||||
|
import OrganizationModal from "./components/Organization/OrganizationModal";
|
||||||
|
import { useAuthModal, useModal } from "./hooks/useAuth";
|
||||||
|
import SwitchTenant from "./pages/authentication/SwitchTenant";
|
||||||
|
import ChangePasswordPage from "./pages/authentication/ChangePassword";
|
||||||
|
import NewCollection from "./components/collections/ManageCollection";
|
||||||
|
|
||||||
|
const ModalProvider = () => {
|
||||||
|
const { isOpen, onClose } = useOrganizationModal();
|
||||||
|
const { isOpen: isAuthOpen } = useAuthModal();
|
||||||
|
const {isOpen:isChangePass} = useModal("ChangePassword")
|
||||||
|
const {isOpen:isCollectionNew} = useModal("newCollection");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isOpen && <OrganizationModal />}
|
||||||
|
{isAuthOpen && <SwitchTenant />}
|
||||||
|
{isChangePass && <ChangePasswordPage /> }
|
||||||
|
{isCollectionNew && <NewCollection/>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalProvider;
|
||||||
@ -12,22 +12,19 @@ import { useQueryClient } from "@tanstack/react-query";
|
|||||||
import eventBus from "../../services/eventBus";
|
import eventBus from "../../services/eventBus";
|
||||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
|
|
||||||
const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizationId, }) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [todayDate, setTodayDate] = useState(new Date());
|
const [todayDate, setTodayDate] = useState(new Date());
|
||||||
const [ShowPending, setShowPending] = useState(false);
|
const [ShowPending, setShowPending] = useState(false);
|
||||||
// const selectedProject = useSelector(
|
|
||||||
// (store) => store.localVariables.projectId
|
|
||||||
// );
|
|
||||||
const selectedProject = useSelectedProject();
|
const selectedProject = useSelectedProject();
|
||||||
const {
|
const {
|
||||||
attendance,
|
attendance,
|
||||||
loading: attLoading,
|
loading: attLoading,
|
||||||
recall: attrecall,
|
recall: attrecall,
|
||||||
isFetching
|
isFetching
|
||||||
} = useAttendance(selectedProject);
|
} = useAttendance(selectedProject, organizationId);
|
||||||
const filteredAttendance = ShowPending
|
const filteredAttendance = ShowPending
|
||||||
? attendance?.filter(
|
? attendance?.filter(
|
||||||
(att) => att?.checkInTime !== null && att?.checkOutTime === null
|
(att) => att?.checkInTime !== null && att?.checkOutTime === null
|
||||||
@ -62,12 +59,11 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
|||||||
const role = item.jobRoleName?.toLowerCase() || "";
|
const role = item.jobRoleName?.toLowerCase() || "";
|
||||||
return (
|
return (
|
||||||
fullName.includes(lowercasedSearchTerm) ||
|
fullName.includes(lowercasedSearchTerm) ||
|
||||||
role.includes(lowercasedSearchTerm) // ✅ also search by role
|
role.includes(lowercasedSearchTerm) // also search by role
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}, [group1, group2, searchTerm]);
|
}, [group1, group2, searchTerm]);
|
||||||
|
|
||||||
|
|
||||||
const { currentPage, totalPages, currentItems, paginate } = usePagination(
|
const { currentPage, totalPages, currentItems, paginate } = usePagination(
|
||||||
finalFilteredData,
|
finalFilteredData,
|
||||||
ITEMS_PER_PAGE
|
ITEMS_PER_PAGE
|
||||||
@ -116,7 +112,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
|||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className="table-responsive text-nowrap h-100"
|
className="table-responsive text-nowrap h-100"
|
||||||
style={{ minHeight: "200px" }} // 🔹 Ensures fixed height
|
style={{ minHeight: "200px" }} // Ensures fixed height
|
||||||
>
|
>
|
||||||
<div className="d-flex text-start align-items-center py-2">
|
<div className="d-flex text-start align-items-center py-2">
|
||||||
<strong>Date : {formatUTCToLocalTime(todayDate)}</strong>
|
<strong>Date : {formatUTCToLocalTime(todayDate)}</strong>
|
||||||
@ -130,7 +126,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
|||||||
checked={ShowPending}
|
checked={ShowPending}
|
||||||
onChange={(e) => setShowPending(e.target.checked)}
|
onChange={(e) => setShowPending(e.target.checked)}
|
||||||
/>
|
/>
|
||||||
<label className="form-check-label ms-0">Show Pending</label>
|
<label className="form-check-label ms-0">Pending Attendance</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{attLoading ? (
|
{attLoading ? (
|
||||||
@ -142,6 +138,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
|||||||
<tr className="border-top-1">
|
<tr className="border-top-1">
|
||||||
<th colSpan={2}>Name</th>
|
<th colSpan={2}>Name</th>
|
||||||
<th>Role</th>
|
<th>Role</th>
|
||||||
|
<th>Organization</th>
|
||||||
<th>
|
<th>
|
||||||
<i className="bx bxs-down-arrow-alt text-success"></i>
|
<i className="bx bxs-down-arrow-alt text-success"></i>
|
||||||
Check-In
|
Check-In
|
||||||
@ -190,6 +187,8 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>{item.jobRoleName}</td>
|
<td>{item.jobRoleName}</td>
|
||||||
|
<td>{item.organizationName || "--"}</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
{item.checkInTime
|
{item.checkInTime
|
||||||
? convertShortTime(item.checkInTime)
|
? convertShortTime(item.checkInTime)
|
||||||
@ -213,14 +212,31 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
|||||||
))}
|
))}
|
||||||
{!attendance && (
|
{!attendance && (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={6} className="text-center text-secondary" style={{ height: "200px" }}>
|
<td
|
||||||
|
colSpan={7}
|
||||||
|
className="text-center text-secondary"
|
||||||
|
style={{ height: "200px" }}
|
||||||
|
>
|
||||||
No employees assigned to the project!
|
No employees assigned to the project!
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className="d-flex justify-content-center align-items-center text-muted"
|
||||||
|
style={{ height: "200px" }}
|
||||||
|
>
|
||||||
|
{searchTerm
|
||||||
|
? "No results found for your search."
|
||||||
|
: attendanceList.length === 0
|
||||||
|
? "No employees assigned to the project."
|
||||||
|
: "No pending records available."}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
{!loading && finalFilteredData.length > ITEMS_PER_PAGE && (
|
{!loading && finalFilteredData.length > ITEMS_PER_PAGE && (
|
||||||
<nav aria-label="Page ">
|
<nav aria-label="Page ">
|
||||||
<ul className="pagination pagination-sm justify-content-end py-1">
|
<ul className="pagination pagination-sm justify-content-end py-1">
|
||||||
@ -264,20 +280,6 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
|||||||
</nav>
|
</nav>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
className="d-flex justify-content-center align-items-center text-muted"
|
|
||||||
style={{ height: "200px" }}
|
|
||||||
>
|
|
||||||
{searchTerm
|
|
||||||
? "No results found for your search."
|
|
||||||
: attendanceList.length === 0
|
|
||||||
? "No employees assigned to the project."
|
|
||||||
: "No pending records available."}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -4,14 +4,18 @@ import Avatar from "../common/Avatar";
|
|||||||
import { convertShortTime } from "../../utils/dateUtils";
|
import { convertShortTime } from "../../utils/dateUtils";
|
||||||
import RenderAttendanceStatus from "./RenderAttendanceStatus";
|
import RenderAttendanceStatus from "./RenderAttendanceStatus";
|
||||||
import { useSelector, useDispatch } from "react-redux";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import { fetchAttendanceData } from "../../slices/apiSlice/attedanceLogsSlice";
|
|
||||||
import DateRangePicker from "../common/DateRangePicker";
|
import DateRangePicker from "../common/DateRangePicker";
|
||||||
import { clearCacheKey, getCachedData, useSelectedProject } from "../../slices/apiDataManager";
|
import {
|
||||||
|
clearCacheKey,
|
||||||
|
getCachedData,
|
||||||
|
useSelectedProject,
|
||||||
|
} from "../../slices/apiDataManager";
|
||||||
import eventBus from "../../services/eventBus";
|
import eventBus from "../../services/eventBus";
|
||||||
import AttendanceRepository from "../../repositories/AttendanceRepository";
|
import AttendanceRepository from "../../repositories/AttendanceRepository";
|
||||||
import { useAttendancesLogs } from "../../hooks/useAttendance";
|
import { useAttendancesLogs } from "../../hooks/useAttendance";
|
||||||
import { queryClient } from "../../layouts/AuthLayout";
|
import { queryClient } from "../../layouts/AuthLayout";
|
||||||
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
const usePagination = (data, itemsPerPage) => {
|
const usePagination = (data, itemsPerPage) => {
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
@ -34,18 +38,14 @@ const usePagination = (data, itemsPerPage) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
|
||||||
// const selectedProject = useSelector(
|
|
||||||
// (store) => store.localVariables.projectId
|
|
||||||
// );
|
|
||||||
const selectedProject = useSelectedProject();
|
const selectedProject = useSelectedProject();
|
||||||
const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" });
|
const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" });
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [showPending, setShowPending] = useState(false)
|
const [showPending, setShowPending] = useState(false);
|
||||||
|
|
||||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||||
const [processedData, setProcessedData] = useState([]);
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
today.setHours(0, 0, 0, 0);
|
today.setHours(0, 0, 0, 0);
|
||||||
@ -68,56 +68,32 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const sortByName = (a, b) => {
|
const sortByName = (a, b) => {
|
||||||
const nameA = a.firstName.toLowerCase() + a.lastName.toLowerCase();
|
const nameA = (a.firstName + a.lastName).toLowerCase();
|
||||||
const nameB = b.firstName.toLowerCase() + b.lastName.toLowerCase();
|
const nameB = (b.firstName + b.lastName).toLowerCase();
|
||||||
return nameA?.localeCompare(nameB);
|
return nameA.localeCompare(nameB);
|
||||||
};
|
};
|
||||||
|
|
||||||
const {
|
const { data = [], isLoading, error, refetch, isFetching } = useAttendancesLogs(
|
||||||
data = [],
|
|
||||||
isLoading,
|
|
||||||
error,
|
|
||||||
refetch,
|
|
||||||
isFetching,
|
|
||||||
} = useAttendancesLogs(
|
|
||||||
selectedProject,
|
selectedProject,
|
||||||
dateRange.startDate,
|
dateRange.startDate,
|
||||||
dateRange.endDate
|
dateRange.endDate,
|
||||||
|
organizationId
|
||||||
);
|
);
|
||||||
const filtering = (data) => {
|
|
||||||
|
const processedData = useMemo(() => {
|
||||||
const filteredData = showPending
|
const filteredData = showPending
|
||||||
? data.filter((item) => item.checkOutTime === null)
|
? data.filter((item) => item.checkOutTime === null)
|
||||||
: data;
|
: data;
|
||||||
|
|
||||||
const group1 = filteredData
|
const group1 = filteredData.filter((d) => d.activity === 1 && isSameDay(d.checkInTime)).sort(sortByName);
|
||||||
.filter((d) => d.activity === 1 && isSameDay(d.checkInTime))
|
const group2 = filteredData.filter((d) => d.activity === 4 && isSameDay(d.checkOutTime)).sort(sortByName);
|
||||||
.sort(sortByName);
|
const group3 = filteredData.filter((d) => d.activity === 1 && isBeforeToday(d.checkInTime)).sort(sortByName);
|
||||||
const group2 = filteredData
|
const group4 = filteredData.filter((d) => d.activity === 4 && isBeforeToday(d.checkOutTime));
|
||||||
.filter((d) => d.activity === 4 && isSameDay(d.checkOutTime))
|
const group5 = filteredData.filter((d) => d.activity === 2 && isBeforeToday(d.checkOutTime)).sort(sortByName);
|
||||||
.sort(sortByName);
|
const group6 = filteredData.filter((d) => d.activity === 5).sort(sortByName);
|
||||||
const group3 = filteredData
|
|
||||||
.filter((d) => d.activity === 1 && isBeforeToday(d.checkInTime))
|
|
||||||
.sort(sortByName);
|
|
||||||
const group4 = filteredData.filter(
|
|
||||||
(d) => d.activity === 4 && isBeforeToday(d.checkOutTime)
|
|
||||||
);
|
|
||||||
const group5 = filteredData
|
|
||||||
.filter((d) => d.activity === 2 && isBeforeToday(d.checkOutTime))
|
|
||||||
.sort(sortByName);
|
|
||||||
const group6 = filteredData
|
|
||||||
.filter((d) => d.activity === 5)
|
|
||||||
.sort(sortByName);
|
|
||||||
|
|
||||||
const sortedList = [
|
const sortedList = [...group1, ...group2, ...group3, ...group4, ...group5, ...group6];
|
||||||
...group1,
|
|
||||||
...group2,
|
|
||||||
...group3,
|
|
||||||
...group4,
|
|
||||||
...group5,
|
|
||||||
...group6,
|
|
||||||
];
|
|
||||||
|
|
||||||
// Group by date
|
|
||||||
const groupedByDate = sortedList.reduce((acc, item) => {
|
const groupedByDate = sortedList.reduce((acc, item) => {
|
||||||
const date = (item.checkInTime || item.checkOutTime)?.split("T")[0];
|
const date = (item.checkInTime || item.checkOutTime)?.split("T")[0];
|
||||||
if (date) {
|
if (date) {
|
||||||
@ -127,28 +103,17 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
|||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
const sortedDates = Object.keys(groupedByDate).sort(
|
const sortedDates = Object.keys(groupedByDate).sort((a, b) => new Date(b) - new Date(a));
|
||||||
(a, b) => new Date(b) - new Date(a)
|
return sortedDates.flatMap((date) => groupedByDate[date]);
|
||||||
);
|
|
||||||
|
|
||||||
const finalData = sortedDates.flatMap((date) => groupedByDate[date]);
|
|
||||||
setProcessedData(finalData);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
filtering(data);
|
|
||||||
}, [data, showPending]);
|
}, [data, showPending]);
|
||||||
|
|
||||||
// New useEffect to handle search filtering
|
|
||||||
const filteredSearchData = useMemo(() => {
|
const filteredSearchData = useMemo(() => {
|
||||||
if (!searchTerm) {
|
if (!searchTerm) return processedData;
|
||||||
return processedData;
|
|
||||||
}
|
const lowercased = searchTerm.toLowerCase();
|
||||||
const lowercasedSearchTerm = searchTerm.toLowerCase();
|
return processedData.filter((item) =>
|
||||||
return processedData.filter((item) => {
|
`${item.firstName} ${item.lastName}`.toLowerCase().includes(lowercased)
|
||||||
const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
|
);
|
||||||
return fullName.includes(lowercasedSearchTerm);
|
|
||||||
});
|
|
||||||
}, [processedData, searchTerm]);
|
}, [processedData, searchTerm]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -161,34 +126,27 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
resetPage();
|
resetPage();
|
||||||
}, [filteredSearchData, resetPage]);
|
}, [filteredSearchData]);
|
||||||
|
|
||||||
const handler = useCallback(
|
const handler = useCallback(
|
||||||
(msg) => {
|
(msg) => {
|
||||||
const { startDate, endDate } = dateRange;
|
const { startDate, endDate } = dateRange;
|
||||||
const checkIn = msg.response.checkInTime.substring(0, 10);
|
const checkIn = msg.response.checkInTime.substring(0, 10);
|
||||||
if (
|
|
||||||
selectedProject === msg.projectId &&
|
if (selectedProject === msg.projectId && startDate <= checkIn && checkIn <= endDate) {
|
||||||
startDate <= checkIn &&
|
|
||||||
checkIn <= endDate
|
|
||||||
) {
|
|
||||||
queryClient.setQueriesData(["attendanceLogs"], (oldData) => {
|
queryClient.setQueriesData(["attendanceLogs"], (oldData) => {
|
||||||
if (!oldData) {
|
if (!oldData) {
|
||||||
queryClient.invalidateQueries({ queryKey: ["attendanceLogs"] });
|
queryClient.invalidateQueries({ queryKey: ["attendanceLogs"] });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const updatedAttendance = oldData.map((record) =>
|
return oldData.map((record) =>
|
||||||
record.id === msg.response.id
|
record.id === msg.response.id ? { ...record, ...msg.response } : record
|
||||||
? { ...record, ...msg.response }
|
|
||||||
: record
|
|
||||||
);
|
);
|
||||||
filtering(updatedAttendance);
|
|
||||||
return updatedAttendance;
|
|
||||||
});
|
});
|
||||||
resetPage();
|
resetPage();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[selectedProject, dateRange, filtering, resetPage]
|
[selectedProject, dateRange, resetPage]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -200,18 +158,10 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
|||||||
(msg) => {
|
(msg) => {
|
||||||
const { startDate, endDate } = dateRange;
|
const { startDate, endDate } = dateRange;
|
||||||
if (data.some((item) => item.employeeId == msg.employeeId)) {
|
if (data.some((item) => item.employeeId == msg.employeeId)) {
|
||||||
// dispatch(
|
refetch();
|
||||||
// fetchAttendanceData({
|
|
||||||
// ,
|
|
||||||
// fromDate: startDate,
|
|
||||||
// toDate: endDate,
|
|
||||||
// })
|
|
||||||
// );
|
|
||||||
|
|
||||||
refetch()
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[selectedProject, dateRange, data, refetch]
|
[data, refetch]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -219,42 +169,51 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
|||||||
return () => eventBus.off("employee", employeeHandler);
|
return () => eventBus.off("employee", employeeHandler);
|
||||||
}, [employeeHandler]);
|
}, [employeeHandler]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className="dataTables_length text-start py-2 d-flex justify-content-between"
|
className="dataTables_length text-start py-2 d-flex flex-wrap justify-content-between"
|
||||||
id="DataTables_Table_0_length"
|
id="DataTables_Table_0_length"
|
||||||
>
|
>
|
||||||
<div className="d-flex align-items-center my-0 ">
|
<div className="d-flex flex-wrap align-items-center gap-2 gap-md-3 my-0">
|
||||||
|
{/* Date Range Picker */}
|
||||||
|
<div className="flex-grow-1 flex-md-grow-0">
|
||||||
<DateRangePicker
|
<DateRangePicker
|
||||||
onRangeChange={setDateRange}
|
onRangeChange={setDateRange}
|
||||||
defaultStartDate={yesterday}
|
defaultStartDate={yesterday}
|
||||||
/>
|
/>
|
||||||
<div className="form-check form-switch text-start m-0 ms-5">
|
</div>
|
||||||
|
|
||||||
|
{/* Pending Attendance Switch */}
|
||||||
|
<div className="form-check form-switch text-start mb-0">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="form-check-input"
|
className="form-check-input"
|
||||||
role="switch"
|
role="switch"
|
||||||
disabled={isFetching}
|
|
||||||
id="inactiveEmployeesCheckbox"
|
id="inactiveEmployeesCheckbox"
|
||||||
|
disabled={isFetching}
|
||||||
checked={showPending}
|
checked={showPending}
|
||||||
onChange={(e) => setShowPending(e.target.checked)}
|
onChange={(e) => setShowPending(e.target.checked)}
|
||||||
/>
|
/>
|
||||||
<label className="form-check-label ms-0">Show Pending</label>
|
<label className="form-check-label ms-0 ms-md-0">
|
||||||
|
Pending Attendance
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-2 m-0 text-end">
|
|
||||||
<i
|
|
||||||
className={`bx bx-refresh cursor-pointer fs-4 ${isFetching ? "spin" : ""
|
|
||||||
}`}
|
|
||||||
title="Refresh"
|
|
||||||
onClick={() => refetch()}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div className="table-responsive text-nowrap" style={{ minHeight: "200px" }}>
|
<div
|
||||||
|
className="table-responsive text-nowrap"
|
||||||
|
style={{ minHeight: "200px" }}
|
||||||
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="d-flex justify-content-center align-items-center" style={{ height: "200px" }}>
|
<div
|
||||||
|
className="d-flex justify-content-center align-items-center"
|
||||||
|
style={{ height: "200px" }}
|
||||||
|
>
|
||||||
<p className="text-secondary">Loading...</p>
|
<p className="text-secondary">Loading...</p>
|
||||||
</div>
|
</div>
|
||||||
) : filteredSearchData?.length > 0 ? (
|
) : filteredSearchData?.length > 0 ? (
|
||||||
@ -265,6 +224,7 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
|||||||
Name
|
Name
|
||||||
</th>
|
</th>
|
||||||
<th className="border-top-1">Date</th>
|
<th className="border-top-1">Date</th>
|
||||||
|
<th>Organization</th>
|
||||||
<th>
|
<th>
|
||||||
<i className="bx bxs-down-arrow-alt text-success"></i>{" "}
|
<i className="bx bxs-down-arrow-alt text-success"></i>{" "}
|
||||||
Check-In
|
Check-In
|
||||||
@ -294,7 +254,7 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
|||||||
key={`header-${currentDate}`}
|
key={`header-${currentDate}`}
|
||||||
className="table-row-header"
|
className="table-row-header"
|
||||||
>
|
>
|
||||||
<td colSpan={6} className="text-start">
|
<td colSpan={8} className="text-start">
|
||||||
<strong>
|
<strong>
|
||||||
{moment(currentDate).format("DD-MM-YYYY")}
|
{moment(currentDate).format("DD-MM-YYYY")}
|
||||||
</strong>
|
</strong>
|
||||||
@ -311,7 +271,12 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
|||||||
lastName={attendance.lastName}
|
lastName={attendance.lastName}
|
||||||
/>
|
/>
|
||||||
<div className="d-flex flex-column">
|
<div className="d-flex flex-column">
|
||||||
<a href="#" className="text-heading text-truncate">
|
<a
|
||||||
|
onClick={() =>
|
||||||
|
navigate(`/employee/${attendance.employeeId}?for=attendance`)
|
||||||
|
}
|
||||||
|
className="text-heading text-truncate cursor-pointer"
|
||||||
|
>
|
||||||
<span className="fw-normal">
|
<span className="fw-normal">
|
||||||
{attendance.firstName} {attendance.lastName}
|
{attendance.firstName} {attendance.lastName}
|
||||||
</span>
|
</span>
|
||||||
@ -324,6 +289,7 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
|||||||
attendance.checkInTime || attendance.checkOutTime
|
attendance.checkInTime || attendance.checkOutTime
|
||||||
).format("DD-MMM-YYYY")}
|
).format("DD-MMM-YYYY")}
|
||||||
</td>
|
</td>
|
||||||
|
<td>{attendance.organizationName || "--"}</td>
|
||||||
<td>{convertShortTime(attendance.checkInTime)}</td>
|
<td>{convertShortTime(attendance.checkInTime)}</td>
|
||||||
<td>
|
<td>
|
||||||
{attendance.checkOutTime
|
{attendance.checkOutTime
|
||||||
@ -345,7 +311,11 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
) : (
|
) : (
|
||||||
<div className="my-4"><span className="text-secondary">No Record Available !</span></div>
|
<div className="my-12">
|
||||||
|
<span className="text-secondary">
|
||||||
|
No attendance record found in selected date range.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{paginatedAttendances?.length == 0 && filteredSearchData?.length > 0 && (
|
{paginatedAttendances?.length == 0 && filteredSearchData?.length > 0 && (
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||||||
import TimePicker from "../common/TimePicker";
|
import TimePicker from "../common/TimePicker";
|
||||||
import { usePositionTracker } from "../../hooks/usePositionTracker";
|
import { usePositionTracker } from "../../hooks/usePositionTracker";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { markAttendance } from "../../slices/apiSlice/attedanceLogsSlice";
|
|
||||||
import showToast from "../../services/toastService";
|
import showToast from "../../services/toastService";
|
||||||
import { checkIfCurrentDate } from "../../utils/dateUtils";
|
import { checkIfCurrentDate } from "../../utils/dateUtils";
|
||||||
import { useMarkAttendance } from "../../hooks/useAttendance";
|
import { useMarkAttendance } from "../../hooks/useAttendance";
|
||||||
@ -34,7 +33,7 @@ const createSchema = (modeldata) => {
|
|||||||
const checkOut = new Date(checkIn);
|
const checkOut = new Date(checkIn);
|
||||||
checkOut.setHours(hour, minute, 0, 0);
|
checkOut.setHours(hour, minute, 0, 0);
|
||||||
|
|
||||||
return checkOut > checkIn;
|
return checkOut >= checkIn;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}, {
|
}, {
|
||||||
@ -97,12 +96,12 @@ const CheckInCheckOut = ({ modeldata, closeModal, handleSubmitForm }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
<form className="row p-2" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="col-12 d-flex justify-content-center">
|
<div className="col-12 d-flex justify-content-center mb-4">
|
||||||
<label className="fs-5 text-dark text-center">
|
<label className="fs-5 tex-semibold text-center">
|
||||||
{modeldata?.checkInTime && !modeldata?.checkOutTime
|
{modeldata?.checkInTime && !modeldata?.checkOutTime
|
||||||
? "Check-out :"
|
? "Check-Out "
|
||||||
: "Check-in :"}
|
: "Check-In "}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import "../../components/Project/ProjectInfra.css";
|
import "../../components/Project/ProjectInfra.css";
|
||||||
import BuildingModel from "../Project/Infrastructure/BuildingModel";
|
import BuildingModel from "../Project/Infrastructure/BuildingModel";
|
||||||
@ -8,61 +7,76 @@ import WorkAreaModel from "../Project/Infrastructure/WorkAreaModel";
|
|||||||
import TaskModel from "../Project/Infrastructure/TaskModel";
|
import TaskModel from "../Project/Infrastructure/TaskModel";
|
||||||
import ProjectRepository from "../../repositories/ProjectRepository";
|
import ProjectRepository from "../../repositories/ProjectRepository";
|
||||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||||
import {useProjectDetails, useProjectInfra, useProjects} from "../../hooks/useProjects";
|
import {
|
||||||
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
|
useCurrentService,
|
||||||
import {APPROVE_TASK, ASSIGN_REPORT_TASK, MANAGE_PROJECT_INFRA} from "../../utils/constants";
|
useProjectDetails,
|
||||||
import {useDispatch, useSelector} from "react-redux";
|
useProjectInfra,
|
||||||
import {useProfile} from "../../hooks/useProfile";
|
useProjects,
|
||||||
import {refreshData, setProjectId} from "../../slices/localVariablesSlice";
|
} from "../../hooks/useProjects";
|
||||||
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
|
import {
|
||||||
|
APPROVE_TASK,
|
||||||
|
ASSIGN_REPORT_TASK,
|
||||||
|
MANAGE_PROJECT_INFRA,
|
||||||
|
} from "../../utils/constants";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { useProfile } from "../../hooks/useProfile";
|
||||||
|
import { refreshData, setProjectId } from "../../slices/localVariablesSlice";
|
||||||
import InfraTable from "../Project/Infrastructure/InfraTable";
|
import InfraTable from "../Project/Infrastructure/InfraTable";
|
||||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
import Loader from "../common/Loader";
|
import Loader from "../common/Loader";
|
||||||
|
|
||||||
|
const InfraPlanning = () => {
|
||||||
const InfraPlanning = () =>
|
const { profile: LoggedUser, refetch: fetchData } = useProfile();
|
||||||
{
|
const dispatch = useDispatch();
|
||||||
const {profile: LoggedUser, refetch : fetchData} = useProfile()
|
|
||||||
const dispatch = useDispatch()
|
|
||||||
// const selectedProject = useSelector((store)=>store.localVariables.projectId)
|
|
||||||
const selectedProject = useSelectedProject();
|
const selectedProject = useSelectedProject();
|
||||||
const {projectInfra, isLoading, error} = useProjectInfra( selectedProject )
|
const selectedService = useCurrentService();
|
||||||
|
|
||||||
|
const { projectInfra, isLoading, isError, error, isFetched } =
|
||||||
|
useProjectInfra(selectedProject, selectedService || "");
|
||||||
|
|
||||||
|
const canManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA);
|
||||||
|
const canApproveTask = useHasUserPermission(APPROVE_TASK);
|
||||||
|
const canReportTask = useHasUserPermission(ASSIGN_REPORT_TASK);
|
||||||
|
|
||||||
const ManageInfra = useHasUserPermission( MANAGE_PROJECT_INFRA )
|
const reloadedData = useSelector((store) => store.localVariables.reload);
|
||||||
const ApprovedTaskRights = useHasUserPermission(APPROVE_TASK)
|
|
||||||
const ReportTaskRights = useHasUserPermission(ASSIGN_REPORT_TASK)
|
|
||||||
const reloadedData = useSelector( ( store ) => store.localVariables.reload )
|
|
||||||
|
|
||||||
|
const hasAccess = canManageInfra || canApproveTask || canReportTask;
|
||||||
|
|
||||||
// useEffect( () =>
|
if (isError) {
|
||||||
// {
|
return <div>{error?.response?.data?.message || error?.message}</div>;
|
||||||
// if (reloadedData)
|
}
|
||||||
// {
|
|
||||||
// refetch()
|
|
||||||
// dispatch( refreshData( false ) )
|
|
||||||
// }
|
|
||||||
|
|
||||||
// },[reloadedData])
|
if (!hasAccess && !isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="text-center">
|
||||||
|
<i className="fa-solid fa-triangle-exclamation fs-5"></i>
|
||||||
|
<p>Access Denied: You don't have permission to perform this action.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <Loader />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFetched && (!projectInfra || projectInfra.length === 0)) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="text-center d-flex justify-content-center align-items-center text-muted"
|
||||||
|
style={{ minHeight: "40vh", fontSize: "0.9rem" }}
|
||||||
|
>
|
||||||
|
<p className="my-3 m-0">No Result Found</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4">
|
<div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4">
|
||||||
<div className="card">
|
|
||||||
<div className="card-body" style={{ padding: "0.5rem" }}>
|
<div className="card-body" style={{ padding: "0.5rem" }}>
|
||||||
{(ApprovedTaskRights || ReportTaskRights) ? (
|
<div className="row">
|
||||||
<div className="align-items-center">
|
<InfraTable buildings={projectInfra} projectId={selectedProject} />
|
||||||
<div className="row ">
|
|
||||||
{isLoading && (<Loader/> )}
|
|
||||||
{( !isLoading && projectInfra?.length === 0 ) && ( <p>No Result Found</p> )}
|
|
||||||
{(!isLoading && projectInfra?.length > 0) && (<InfraTable buildings={projectInfra} projectId={selectedProject}/>)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="text-center">
|
|
||||||
<i className="fa-solid fa-triangle-exclamation fs-5"></i>
|
|
||||||
<p>Access Denied: You don't have permission to perform this action. !</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,25 +1,45 @@
|
|||||||
import React, { useCallback, useEffect, useState, useMemo } from "react";
|
import React, { useCallback, useEffect, useState, useMemo } from "react";
|
||||||
import Avatar from "../common/Avatar";
|
import Avatar from "../common/Avatar";
|
||||||
import { convertShortTime } from "../../utils/dateUtils";
|
import { convertShortTime, formatUTCToLocalTime } from "../../utils/dateUtils";
|
||||||
import RegularizationActions from "./RegularizationActions";
|
import RegularizationActions from "./RegularizationActions";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { useRegularizationRequests } from "../../hooks/useAttendance";
|
import { useRegularizationRequests } from "../../hooks/useAttendance";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import usePagination from "../../hooks/usePagination";
|
import usePagination from "../../hooks/usePagination";
|
||||||
import eventBus from "../../services/eventBus";
|
import eventBus from "../../services/eventBus";
|
||||||
import { cacheData, clearCacheKey, useSelectedProject } from "../../slices/apiDataManager";
|
import {
|
||||||
|
cacheData,
|
||||||
|
clearCacheKey,
|
||||||
|
useSelectedProject,
|
||||||
|
} from "../../slices/apiDataManager";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
import Pagination from "../../components/common/Pagination";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
const Regularization = ({ handleRequest, searchTerm }) => {
|
const Regularization = ({
|
||||||
|
handleRequest,
|
||||||
|
searchTerm,
|
||||||
|
projectId,
|
||||||
|
organizationId,
|
||||||
|
IncludeInActive,
|
||||||
|
}) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
// var selectedProject = useSelector((store) => store.localVariables.projectId);
|
// var selectedProject = useSelector((store) => store.localVariables.projectId);
|
||||||
const selectedProject = useSelectedProject();
|
const selectedProject = useSelectedProject();
|
||||||
const [regularizesList, setregularizedList] = useState([]);
|
const [regularizesList, setregularizedList] = useState([]);
|
||||||
const { regularizes, loading, error, refetch } =
|
const navigate = useNavigate();
|
||||||
useRegularizationRequests(selectedProject);
|
const { regularizes, loading, error, refetch } = useRegularizationRequests(
|
||||||
|
selectedProject,
|
||||||
|
organizationId,
|
||||||
|
IncludeInActive
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!regularizes) return
|
||||||
|
if (regularizes?.length) {
|
||||||
setregularizedList(regularizes);
|
setregularizedList(regularizes);
|
||||||
|
|
||||||
|
}
|
||||||
}, [regularizes]);
|
}, [regularizes]);
|
||||||
|
|
||||||
const sortByName = (a, b) => {
|
const sortByName = (a, b) => {
|
||||||
@ -54,18 +74,15 @@ const Regularization = ({ handleRequest, searchTerm }) => {
|
|||||||
}
|
}
|
||||||
const lowercasedSearchTerm = searchTerm.toLowerCase();
|
const lowercasedSearchTerm = searchTerm.toLowerCase();
|
||||||
return sortedList.filter((item) => {
|
return sortedList.filter((item) => {
|
||||||
const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
|
const fullName = `${item?.firstName} ${item?.lastName}`.toLowerCase();
|
||||||
return fullName.includes(lowercasedSearchTerm);
|
return fullName.includes(lowercasedSearchTerm);
|
||||||
});
|
});
|
||||||
}, [regularizesList, searchTerm]);
|
}, [regularizesList, searchTerm]);
|
||||||
|
|
||||||
const { currentPage, totalPages, currentItems, paginate } =
|
const { currentPage, totalPages, currentItems, paginate } = usePagination(
|
||||||
usePagination(filteredSearchData, 20);
|
filteredSearchData,
|
||||||
|
20
|
||||||
// Reset pagination when the search term or data changes
|
);
|
||||||
useEffect(() => {
|
|
||||||
|
|
||||||
}, [filteredSearchData]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
eventBus.on("regularization", handler);
|
eventBus.on("regularization", handler);
|
||||||
@ -87,9 +104,16 @@ const Regularization = ({ handleRequest, searchTerm }) => {
|
|||||||
}, [employeeHandler]);
|
}, [employeeHandler]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="table-responsive text-nowrap pb-4" style={{ minHeight: "200px" }}>
|
<div>
|
||||||
|
<div
|
||||||
|
className="table-responsive pt-3 text-nowrap pb-4"
|
||||||
|
style={{ minHeight: "200px" }}
|
||||||
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="d-flex justify-content-center align-items-center" style={{ height: "200px" }}>
|
<div
|
||||||
|
className="d-flex justify-content-center align-items-center"
|
||||||
|
style={{ height: "200px" }}
|
||||||
|
>
|
||||||
<p className="text-secondary">Loading...</p>
|
<p className="text-secondary">Loading...</p>
|
||||||
</div>
|
</div>
|
||||||
) : currentItems?.length > 0 ? (
|
) : currentItems?.length > 0 ? (
|
||||||
@ -98,12 +122,19 @@ const Regularization = ({ handleRequest, searchTerm }) => {
|
|||||||
<tr>
|
<tr>
|
||||||
<th colSpan={2}>Name</th>
|
<th colSpan={2}>Name</th>
|
||||||
<th>Date</th>
|
<th>Date</th>
|
||||||
|
<th>Organization</th>
|
||||||
<th>
|
<th>
|
||||||
<i className="bx bxs-down-arrow-alt text-success"></i>Check-In
|
<i className="bx bxs-down-arrow-alt text-success"></i>Check-In
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
<i className="bx bxs-up-arrow-alt text-danger"></i>Check-Out
|
<i className="bx bxs-up-arrow-alt text-danger"></i>Check-Out
|
||||||
</th>
|
</th>
|
||||||
|
<th colSpan={2}>
|
||||||
|
Requested By
|
||||||
|
</th>
|
||||||
|
<th >
|
||||||
|
Requested At
|
||||||
|
</th>
|
||||||
<th>Action</th>
|
<th>Action</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -112,12 +143,12 @@ const Regularization = ({ handleRequest, searchTerm }) => {
|
|||||||
<tr key={index}>
|
<tr key={index}>
|
||||||
<td colSpan={2}>
|
<td colSpan={2}>
|
||||||
<div className="d-flex justify-content-start align-items-center">
|
<div className="d-flex justify-content-start align-items-center">
|
||||||
<Avatar
|
<Avatar firstName={att.firstName} lastName={att.lastName} />
|
||||||
firstName={att.firstName}
|
<div className="d-flex flex-column"> <a
|
||||||
lastName={att.lastName}
|
onClick={() =>
|
||||||
></Avatar>
|
navigate(`/employee/${att.employeeId}?for=attendance`)
|
||||||
<div className="d-flex flex-column">
|
}
|
||||||
<a href="#" className="text-heading text-truncate">
|
className="text-heading text-truncate cursor-pointer" >
|
||||||
<span className="fw-normal">
|
<span className="fw-normal">
|
||||||
{att.firstName} {att.lastName}
|
{att.firstName} {att.lastName}
|
||||||
</span>
|
</span>
|
||||||
@ -126,9 +157,28 @@ const Regularization = ({ handleRequest, searchTerm }) => {
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>{moment(att.checkOutTime).format("DD-MMM-YYYY")}</td>
|
<td>{moment(att.checkOutTime).format("DD-MMM-YYYY")}</td>
|
||||||
|
|
||||||
|
<td>{att.organizationName || "--"}</td>
|
||||||
|
|
||||||
<td>{convertShortTime(att.checkInTime)}</td>
|
<td>{convertShortTime(att.checkInTime)}</td>
|
||||||
<td>
|
<td>
|
||||||
{att.checkOutTime ? convertShortTime(att.checkOutTime) : "--"}
|
{att.requestedAt ? convertShortTime(att.checkOutTime) : "--"}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td colSpan={2}>
|
||||||
|
{att.requestedBy ? (<div className="d-flex justify-content-start align-items-center">
|
||||||
|
<Avatar firstName={att?.requestedBy?.firstName} lastName={att?.requestedBy?.lastName} />
|
||||||
|
<div className="d-flex flex-column">
|
||||||
|
<a href="#" className="text-heading text-truncate">
|
||||||
|
<span className="fw-normal">
|
||||||
|
{att?.requestedBy?.firstName} {att?.requestedBy?.lastName}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>) : (<small>--</small>)}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{att?.requestedAt ? formatUTCToLocalTime(att.requestedAt, true) : "--"}
|
||||||
</td>
|
</td>
|
||||||
<td className="text-center ">
|
<td className="text-center ">
|
||||||
<RegularizationActions
|
<RegularizationActions
|
||||||
@ -136,7 +186,6 @@ const Regularization = ({ handleRequest, searchTerm }) => {
|
|||||||
handleRequest={handleRequest}
|
handleRequest={handleRequest}
|
||||||
refresh={refetch}
|
refresh={refetch}
|
||||||
/>
|
/>
|
||||||
{/* </div> */}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
@ -154,45 +203,15 @@ const Regularization = ({ handleRequest, searchTerm }) => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!loading && totalPages > 1 && (
|
</div>
|
||||||
<nav aria-label="Page ">
|
{totalPages > 0 && (
|
||||||
<ul className="pagination pagination-sm justify-content-end py-1 mt-3">
|
<Pagination
|
||||||
<li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}>
|
currentPage={currentPage}
|
||||||
<button
|
totalPages={totalPages}
|
||||||
className="page-link btn-xs"
|
onPageChange={paginate}
|
||||||
onClick={() => paginate(currentPage - 1)}
|
/>
|
||||||
>
|
|
||||||
«
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{[...Array(totalPages)].map((_, index) => (
|
|
||||||
<li
|
|
||||||
key={index}
|
|
||||||
className={`page-item ${currentPage === index + 1 ? "active" : ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="page-link "
|
|
||||||
onClick={() => paginate(index + 1)}
|
|
||||||
>
|
|
||||||
{index + 1}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
<li
|
|
||||||
className={`page-item ${currentPage === totalPages ? "disabled" : ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="page-link "
|
|
||||||
onClick={() => paginate(currentPage + 1)}
|
|
||||||
>
|
|
||||||
»
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
import React, { act, useEffect, useState } from 'react'
|
import React, { act, useEffect, useState } from 'react'
|
||||||
import useAttendanceStatus, { ACTIONS } from '../../hooks/useAttendanceStatus';
|
import useAttendanceStatus, { ACTIONS } from '../../hooks/useAttendanceStatus';
|
||||||
// import AttendanceRepository from '../../repositories/AttendanceRepository';
|
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { usePositionTracker } from '../../hooks/usePositionTracker';
|
import { usePositionTracker } from '../../hooks/usePositionTracker';
|
||||||
import {markCurrentAttendance} from '../../slices/apiSlice/attendanceAllSlice';
|
|
||||||
import {cacheData, getCachedData, useSelectedProject} from '../../slices/apiDataManager';
|
import {cacheData, getCachedData, useSelectedProject} from '../../slices/apiDataManager';
|
||||||
import showToast from '../../services/toastService';
|
import showToast from '../../services/toastService';
|
||||||
import { useMarkAttendance } from '../../hooks/useAttendance';
|
import { useMarkAttendance } from '../../hooks/useAttendance';
|
||||||
|
|||||||
@ -77,7 +77,7 @@ export const ReportTask = ({ report, closeModal }) => {
|
|||||||
return (
|
return (
|
||||||
<div className="container m-0">
|
<div className="container m-0">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="fs-6 fw-semibold">Report Task</p>
|
<p className="fs-5 fw-semibold">Report Task</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-1 row text-start">
|
<div className="mb-1 row text-start">
|
||||||
<label htmlFor="html5-text-input" className="col-md-4 col-form-label">
|
<label htmlFor="html5-text-input" className="col-md-4 col-form-label">
|
||||||
@ -101,16 +101,14 @@ export const ReportTask = ({ report, closeModal }) => {
|
|||||||
<label htmlFor="html5-email-input" className="col-md-4 col-form-label">
|
<label htmlFor="html5-email-input" className="col-md-4 col-form-label">
|
||||||
Wrok Area :
|
Wrok Area :
|
||||||
</label>
|
</label>
|
||||||
<div className="col-md-8 text-start text-wrap">
|
<div className="col-md-8 text-start">
|
||||||
<label className=" col-form-label">
|
<div className="text-wrap">
|
||||||
{" "}
|
{report?.workItem?.workArea?.floor?.building?.name} <i className="bx bx-chevron-right"></i>
|
||||||
{report?.workItem?.workArea?.floor?.building?.name}{" "}
|
{report?.workItem?.workArea?.floor?.floorName} <i className="bx bx-chevron-right"></i>
|
||||||
<i className="bx bx-chevron-right"></i>{" "}
|
|
||||||
{report?.workItem?.workArea?.floor?.floorName}{" "}
|
|
||||||
<i className="bx bx-chevron-right"> </i>
|
|
||||||
{report?.workItem?.workArea?.areaName}
|
{report?.workItem?.workArea?.areaName}
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-1 row text-start">
|
<div className="mb-1 row text-start">
|
||||||
<label htmlFor="html5-email-input" className="col-md-4 col-form-label">
|
<label htmlFor="html5-email-input" className="col-md-4 col-form-label">
|
||||||
|
|||||||
@ -110,6 +110,8 @@ const ReportTaskComments = ({
|
|||||||
approvedTask: defaultCompletedTask || 0,
|
approvedTask: defaultCompletedTask || 0,
|
||||||
});
|
});
|
||||||
}, [defaultCompletedTask]);
|
}, [defaultCompletedTask]);
|
||||||
|
|
||||||
|
const completed_Task = watch("approvedTask")
|
||||||
return (
|
return (
|
||||||
<div className="p-2 p-sm-1">
|
<div className="p-2 p-sm-1">
|
||||||
<div className="modal-body p-sm-4 p-0">
|
<div className="modal-body p-sm-4 p-0">
|
||||||
@ -339,13 +341,13 @@ const ReportTaskComments = ({
|
|||||||
<div
|
<div
|
||||||
className={` ${
|
className={` ${
|
||||||
actionAllow && !commentsData.approvedBy
|
actionAllow && !commentsData.approvedBy
|
||||||
? " d-flex justify-content-between"
|
? " d-flex justify-content-between align-items-center"
|
||||||
: "text-end"
|
: "text-end"
|
||||||
} mt-2`}
|
} mt-2`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`form-check ${
|
className={`form-check ${
|
||||||
!(actionAllow && !commentsData.approvedBy) && "d-none"
|
!(actionAllow && !commentsData.approvedBy && defaultCompletedTask > completed_Task ) && "d-none"
|
||||||
} `}
|
} `}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||||||
import { string, z } from "zod";
|
import { string, z } from "zod";
|
||||||
import {
|
import {
|
||||||
useActivitiesMaster,
|
useActivitiesMaster,
|
||||||
|
useServices,
|
||||||
useWorkCategoriesMaster,
|
useWorkCategoriesMaster,
|
||||||
} from "../../hooks/masterHook/useMaster";
|
} from "../../hooks/masterHook/useMaster";
|
||||||
import showToast from "../../services/toastService";
|
import showToast from "../../services/toastService";
|
||||||
@ -25,6 +26,8 @@ const SubTask = ({ activity, onClose }) => {
|
|||||||
const { activities, loading } = useActivitiesMaster();
|
const { activities, loading } = useActivitiesMaster();
|
||||||
const { categories, categoryLoading } = useWorkCategoriesMaster();
|
const { categories, categoryLoading } = useWorkCategoriesMaster();
|
||||||
const { Task, loading: TaskLoading } = useTaskById(activity?.id);
|
const { Task, loading: TaskLoading } = useTaskById(activity?.id);
|
||||||
|
const {data,isError,isLoading,error} = useServices();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@ -97,8 +100,8 @@ const SubTask = ({ activity, onClose }) => {
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className="container-xxl my-1">
|
<div className="container-xxl my-1">
|
||||||
<p className="fw-semibold">Create Sub Task</p>
|
<p className="fw-semibold fs-5">Create Sub Task</p>
|
||||||
<form className="row g-2" onSubmit={handleSubmit(onSubmitForm)}>
|
<form className="row g-2 text-start" onSubmit={handleSubmit(onSubmitForm)}>
|
||||||
<div className="col-6">
|
<div className="col-6">
|
||||||
<label className="form-label">Building</label>
|
<label className="form-label">Building</label>
|
||||||
<input
|
<input
|
||||||
@ -128,26 +131,14 @@ const SubTask = ({ activity, onClose }) => {
|
|||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-12">
|
<div className="col-12">
|
||||||
<label className="form-label">Work Category</label>
|
<label className="form-label">Service</label>
|
||||||
<select
|
<input
|
||||||
className="form-select form-select-sm"
|
type="text"
|
||||||
{...register("workCategoryId")}
|
className="form-control form-control-sm"
|
||||||
onChange={handleCategoryChange}
|
value={activity?.workItem?.activityMaster?.activityGroup?.service?.name || ""}
|
||||||
>
|
disabled
|
||||||
<option value="">
|
/>
|
||||||
{categoryLoading ? "Loading..." : "-- Select Category --"}
|
|
||||||
</option>
|
|
||||||
{categoryData.map((category) => (
|
|
||||||
<option key={category.id} value={category.id}>
|
|
||||||
{category.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
{errors.workCategoryId && (
|
|
||||||
<div className="danger-text">{errors.workCategoryId.message}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="col-12">
|
<div className="col-12">
|
||||||
<label className="form-label">Select Activity</label>
|
<label className="form-label">Select Activity</label>
|
||||||
@ -172,6 +163,27 @@ const SubTask = ({ activity, onClose }) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="col-12">
|
||||||
|
<label className="form-label">Work Category</label>
|
||||||
|
<select
|
||||||
|
className="form-select form-select-sm"
|
||||||
|
{...register("workCategoryId")}
|
||||||
|
onChange={handleCategoryChange}
|
||||||
|
>
|
||||||
|
<option value="">
|
||||||
|
{categoryLoading ? "Loading..." : "-- Select Category --"}
|
||||||
|
</option>
|
||||||
|
{categoryData.map((category) => (
|
||||||
|
<option key={category.id} value={category.id}>
|
||||||
|
{category.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
{errors.workCategoryId && (
|
||||||
|
<div className="danger-text">{errors.workCategoryId.message}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="col-4">
|
<div className="col-4">
|
||||||
<label className="form-label">Planned Work</label>
|
<label className="form-label">Planned Work</label>
|
||||||
<input
|
<input
|
||||||
@ -219,7 +231,15 @@ const SubTask = ({ activity, onClose }) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-12 text-center">
|
<div className="d-flex flex-row gap-3 justify-content-end py-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-sm btn-label-secondary"
|
||||||
|
onClick={() => onClose()}
|
||||||
|
disabled={isPending}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="btn btn-sm btn-primary me-2"
|
className="btn btn-sm btn-primary me-2"
|
||||||
@ -227,14 +247,7 @@ const SubTask = ({ activity, onClose }) => {
|
|||||||
>
|
>
|
||||||
{isPending ? "Please wait..." : "Submit"}
|
{isPending ? "Please wait..." : "Submit"}
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-sm btn-secondary"
|
|
||||||
onClick={() => onClose()}
|
|
||||||
disabled={isPending}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -23,7 +23,7 @@ const HorizontalBarChart = ({
|
|||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-[380px] flex items-center justify-center bg-gray-100 rounded-xl">
|
<div className="w-full h-[380px] flex items-center justify-center bg-gray-100 rounded-xl">
|
||||||
<span className="text-gray-500 text-sm">Loading chart...</span>
|
<span className="text-gray-500">Loading chart...</span>
|
||||||
{/* Replace this with a skeleton or spinner if you prefer */}
|
{/* Replace this with a skeleton or spinner if you prefer */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,42 +0,0 @@
|
|||||||
import React, { createContext, useState, useContext } from "react";
|
|
||||||
import ChangePasswordPage from "../../pages/authentication/ChangePassword";
|
|
||||||
|
|
||||||
const ChangePasswordContext = createContext();
|
|
||||||
|
|
||||||
export const ChangePasswordProvider = ({ children }) => {
|
|
||||||
const [isChangePasswordOpen, setIsChangePasswordOpen] = useState(false);
|
|
||||||
|
|
||||||
const openChangePassword = () => setIsChangePasswordOpen(true);
|
|
||||||
const closeChangePassword = () => setIsChangePasswordOpen(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ChangePasswordContext.Provider
|
|
||||||
value={{ isChangePasswordOpen, openChangePassword, closeChangePassword }}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
|
|
||||||
{isChangePasswordOpen && (
|
|
||||||
<>
|
|
||||||
{/* This is the main Bootstrap modal container */}
|
|
||||||
{/* It provides the fixed positioning and high z-index */}
|
|
||||||
<div
|
|
||||||
className="modal fade show" // 'fade' for animation, 'show' to make it visible
|
|
||||||
style={{ display: 'block' }} // Explicitly set display: block for immediate visibility
|
|
||||||
tabIndex="-1" // Makes the modal focusable
|
|
||||||
role="dialog" // ARIA role for accessibility
|
|
||||||
aria-labelledby="changePasswordModalLabel" // Link to a heading for accessibility
|
|
||||||
aria-modal="true" // Indicate it's a modal dialog
|
|
||||||
>
|
|
||||||
{/* The ChangePasswordPage component itself contains the modal-dialog and modal-content */}
|
|
||||||
<ChangePasswordPage onClose={closeChangePassword} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* The modal backdrop */}
|
|
||||||
<div className="modal-backdrop fade show"></div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</ChangePasswordContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useChangePassword = () => useContext(ChangePasswordContext);
|
|
||||||
111
src/components/DailyProgressRport/TaskReportFilterPanel.jsx
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { useCurrentService } from "../../hooks/useProjects";
|
||||||
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
|
import { FormProvider, useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import {
|
||||||
|
TaskReportDefaultValue,
|
||||||
|
TaskReportFilterSchema,
|
||||||
|
} from "./TaskRportScheam";
|
||||||
|
import { DateRangePicker1 } from "../common/DateRangePicker";
|
||||||
|
import SelectMultiple from "../common/SelectMultiple";
|
||||||
|
import { localToUtc } from "../../utils/appUtils";
|
||||||
|
import { useTaskFilter } from "../../hooks/useTasks";
|
||||||
|
|
||||||
|
const TaskReportFilterPanel = ({ handleFilter }) => {
|
||||||
|
const [resetKey, setResetKey] = useState(0);
|
||||||
|
const selectedProject = useSelectedProject();
|
||||||
|
const selectedService = useCurrentService();
|
||||||
|
const { data } = useTaskFilter(selectedProject);
|
||||||
|
|
||||||
|
const methods = useForm({
|
||||||
|
resolver: zodResolver(TaskReportFilterSchema),
|
||||||
|
defaultValues: TaskReportDefaultValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
reset,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
} = methods;
|
||||||
|
const closePanel = () => {
|
||||||
|
document.querySelector(".offcanvas.show .btn-close")?.click();
|
||||||
|
};
|
||||||
|
const onSubmit = (formData) => {
|
||||||
|
const filterPayload = {
|
||||||
|
...formData,
|
||||||
|
dateFrom: localToUtc(formData.dateFrom),
|
||||||
|
dateTo: localToUtc(formData.dateTo),
|
||||||
|
};
|
||||||
|
handleFilter(filterPayload);
|
||||||
|
closePanel();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClear = () => {
|
||||||
|
setResetKey((prev) => prev + 1);
|
||||||
|
handleFilter(TaskReportDefaultValue);
|
||||||
|
reset(TaskReportDefaultValue);
|
||||||
|
closePanel();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<FormProvider {...methods}>
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className="p-2 text-start">
|
||||||
|
<div className="mb-3 w-100">
|
||||||
|
<label className="fw-semibold">Choose Date Range:</label>
|
||||||
|
<DateRangePicker1
|
||||||
|
placeholder="DD-MM-YYYY To DD-MM-YYYY"
|
||||||
|
startField="dateFrom"
|
||||||
|
endField="dateTo"
|
||||||
|
resetSignal={resetKey}
|
||||||
|
defaultRange={false}
|
||||||
|
maxDate={new Date()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="row mb-2">
|
||||||
|
<SelectMultiple
|
||||||
|
name="buildingIds"
|
||||||
|
label="Building"
|
||||||
|
options={data?.buildings}
|
||||||
|
labelKey="name"
|
||||||
|
valueKey="id"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="row mb-2">
|
||||||
|
<SelectMultiple
|
||||||
|
name="floorIds"
|
||||||
|
label="Floor"
|
||||||
|
options={data?.floors}
|
||||||
|
labelKey="name"
|
||||||
|
valueKey="id"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="row mb-2">
|
||||||
|
<SelectMultiple
|
||||||
|
name="activityIds"
|
||||||
|
label="Activities"
|
||||||
|
options={data?.activities}
|
||||||
|
labelKey="name"
|
||||||
|
valueKey="id"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="d-flex justify-content-end py-3 gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-label-secondary btn-sm"
|
||||||
|
onClick={onClear}
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</button>
|
||||||
|
<button type="submit" className="btn btn-primary btn-sm">
|
||||||
|
Apply
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TaskReportFilterPanel;
|
||||||
306
src/components/DailyProgressRport/TaskReportList.jsx
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
import React, { useState, useEffect, useMemo } from "react";
|
||||||
|
import { useTaskList } from "../../hooks/useTasks";
|
||||||
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
|
import { useProjectName } from "../../hooks/useProjects";
|
||||||
|
import DailyProgrssReport, {
|
||||||
|
useDailyProgrssContext,
|
||||||
|
} from "../../pages/DailyProgressReport/DailyProgrssReport";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||||
|
import {
|
||||||
|
APPROVE_TASK,
|
||||||
|
ASSIGN_REPORT_TASK,
|
||||||
|
ITEMS_PER_PAGE,
|
||||||
|
} from "../../utils/constants";
|
||||||
|
import { formatNumber, formatUTCToLocalTime } from "../../utils/dateUtils";
|
||||||
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
|
import Pagination from "../common/Pagination";
|
||||||
|
import { TaskReportListSkeleton } from "./TaskRepprtListSkeleton";
|
||||||
|
import HoverPopup from "../common/HoverPopup";
|
||||||
|
|
||||||
|
const TaskReportList = () => {
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [filters, setFilters] = useState({
|
||||||
|
selectedBuilding: "",
|
||||||
|
selectedFloors: [],
|
||||||
|
selectedActivities: [],
|
||||||
|
});
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const ApprovedTaskRights = useHasUserPermission(APPROVE_TASK);
|
||||||
|
const ReportTaskRights = useHasUserPermission(ASSIGN_REPORT_TASK);
|
||||||
|
|
||||||
|
const { service, openModal, closeModal, filter } = useDailyProgrssContext();
|
||||||
|
const selectedProject = useSelectedProject();
|
||||||
|
const { projectNames } = useProjectName();
|
||||||
|
|
||||||
|
const { data, isLoading, isError, error } = useTaskList(
|
||||||
|
selectedProject,
|
||||||
|
ITEMS_PER_PAGE,
|
||||||
|
currentPage,
|
||||||
|
service, filter
|
||||||
|
);
|
||||||
|
|
||||||
|
const ProgrssReportColumn = [
|
||||||
|
{
|
||||||
|
key: "activity",
|
||||||
|
label: "Activity",
|
||||||
|
getValue: (task) => task.workItem.activityMaster?.activityName || "N/A",
|
||||||
|
align: "text-start",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "assigned",
|
||||||
|
label: "Total Assigned",
|
||||||
|
getValue: (task) => task.plannedTask ?? "N/A",
|
||||||
|
align: "text-start",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "completed",
|
||||||
|
label: "Completed",
|
||||||
|
getValue: (task) => task.completedTask ?? "N/A",
|
||||||
|
align: "text-start",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "assignAt",
|
||||||
|
label: "Assign Date",
|
||||||
|
getValue: (task) =>
|
||||||
|
task.assignmentDate ? formatUTCToLocalTime(task.assignmentDate) : "N/A",
|
||||||
|
align: "text-start",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "team",
|
||||||
|
label: "Team",
|
||||||
|
getValue: (task) =>
|
||||||
|
task.teamMembers?.map((m) => `${m.firstName} ${m.lastName}`).join(", ") ||
|
||||||
|
"N/A",
|
||||||
|
align: "text-start",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const paginate = (page) => {
|
||||||
|
if (page >= 1 && page <= (data?.totalPages ?? 1)) {
|
||||||
|
setCurrentPage(page);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!selectedProject && projectNames.length > 0) {
|
||||||
|
dispatch(setProjectId(projectNames[0].id));
|
||||||
|
}
|
||||||
|
}, [selectedProject, projectNames, dispatch]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFilters({
|
||||||
|
selectedBuilding: "",
|
||||||
|
selectedFloors: [],
|
||||||
|
selectedActivities: [],
|
||||||
|
});
|
||||||
|
}, [selectedProject]);
|
||||||
|
|
||||||
|
// Filter and Group wise data
|
||||||
|
|
||||||
|
const filteredTasks = useMemo(() => {
|
||||||
|
if (!data?.data) return [];
|
||||||
|
return data?.data.filter((task) => {
|
||||||
|
const { selectedBuilding, selectedFloors, selectedActivities } = filters;
|
||||||
|
|
||||||
|
if (
|
||||||
|
selectedBuilding &&
|
||||||
|
task?.workItem?.workArea?.floor?.building?.name !== selectedBuilding
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
if (
|
||||||
|
selectedFloors.length > 0 &&
|
||||||
|
!selectedFloors.includes(task?.workItem?.workArea?.floor?.floorName)
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
if (
|
||||||
|
selectedActivities.length > 0 &&
|
||||||
|
!selectedActivities.includes(
|
||||||
|
task?.workItem?.activityMaster?.activityName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}, [data?.data, filters, currentPage]);
|
||||||
|
|
||||||
|
const groupedTasks = useMemo(() => {
|
||||||
|
const groups = {};
|
||||||
|
filteredTasks.forEach((task) => {
|
||||||
|
const date = task.assignmentDate.split("T")[0];
|
||||||
|
if (!groups[date]) groups[date] = [];
|
||||||
|
groups[date].push(task);
|
||||||
|
});
|
||||||
|
return Object.keys(groups)
|
||||||
|
.sort((a, b) => new Date(b) - new Date(a))
|
||||||
|
.map((date) => ({ date, tasks: groups[date] }));
|
||||||
|
}, [filteredTasks, paginate, currentPage, selectedProject]);
|
||||||
|
|
||||||
|
const renderTeamMembers = (task, refIndex) => (
|
||||||
|
<div
|
||||||
|
key={refIndex}
|
||||||
|
tabIndex="0"
|
||||||
|
className="d-flex align-items-center avatar-group justify-content-center"
|
||||||
|
data-bs-toggle="popover"
|
||||||
|
data-bs-trigger="focus"
|
||||||
|
data-bs-placement="left"
|
||||||
|
data-bs-html="true"
|
||||||
|
data-bs-content={`
|
||||||
|
<div class="border border-secondary rounded custom-popover p-2 px-3">
|
||||||
|
${task.teamMembers
|
||||||
|
.map(
|
||||||
|
(m) => `
|
||||||
|
<div class="d-flex align-items-center gap-2 mb-2">
|
||||||
|
<div class="avatar avatar-xs">
|
||||||
|
<span class="avatar-initial rounded-circle bg-label-primary">
|
||||||
|
${m?.firstName?.charAt(0) || ""}${m?.lastName?.charAt(0) || ""
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span>${m.firstName} ${m.lastName}</span>
|
||||||
|
</div>`
|
||||||
|
)
|
||||||
|
.join("")}
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{task.teamMembers.slice(0, 3).map((m) => (
|
||||||
|
<div
|
||||||
|
key={m.id}
|
||||||
|
className="avatar avatar-xs"
|
||||||
|
title={`${m.firstName} ${m.lastName}`}
|
||||||
|
>
|
||||||
|
<span className="avatar-initial rounded-circle bg-label-primary">
|
||||||
|
{m?.firstName.slice(0, 1)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{task.teamMembers.length > 3 && (
|
||||||
|
<div
|
||||||
|
className="avatar avatar-xs"
|
||||||
|
title={`${task.teamMembers.length - 3} more`}
|
||||||
|
>
|
||||||
|
<span className="avatar-initial rounded-circle bg-label-secondary">
|
||||||
|
+{task.teamMembers.length - 3}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isLoading) return <TaskReportListSkeleton />;
|
||||||
|
if (isError) return <div>Loading....</div>;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="mt-2 table-responsive text-nowrap">
|
||||||
|
<table className="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="text-start">Activity</th>
|
||||||
|
<th>
|
||||||
|
<span>
|
||||||
|
Total Pending{" "}
|
||||||
|
<HoverPopup
|
||||||
|
title="Total Pending Task"
|
||||||
|
content={<p>This shows the total pending tasks for each activity on that date.</p>}
|
||||||
|
>
|
||||||
|
<i className="bx bx-xs ms-1 bx-info-circle cursor-pointer"></i>
|
||||||
|
</HoverPopup>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<span>
|
||||||
|
Reported/Planned{" "}
|
||||||
|
<HoverPopup
|
||||||
|
title="Reported and Planned Task"
|
||||||
|
content={<p>This shows the reported versus planned tasks for each activity on that date.</p>}
|
||||||
|
>
|
||||||
|
<i className="bx bx-xs ms-1 bx-info-circle cursor-pointer"></i>
|
||||||
|
</HoverPopup>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th>Assign Date</th>
|
||||||
|
<th>Team</th>
|
||||||
|
<th className="text-center">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{groupedTasks.length === 0 && (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={6} className="text-center align-middle" style={{ height: "200px", borderBottom: "none" }}>
|
||||||
|
No reports available
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{groupedTasks.map(({ date, tasks }) => (
|
||||||
|
<React.Fragment key={date}>
|
||||||
|
<tr className="table-row-header text-start">
|
||||||
|
<td colSpan={6}>
|
||||||
|
<strong>{formatUTCToLocalTime(date)}</strong>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{tasks.map((task, idx) => (
|
||||||
|
<tr key={task.id || idx}>
|
||||||
|
<td className="flex-wrap text-start">
|
||||||
|
<div>
|
||||||
|
{task.workItem.activityMaster?.activityName || "No Activity Name"}
|
||||||
|
</div>
|
||||||
|
<div className="text-sm py-2">
|
||||||
|
{task.workItem.workArea?.floor?.building?.name} ›{" "}
|
||||||
|
{task.workItem.workArea?.floor?.floorName} ›{" "}
|
||||||
|
{task.workItem.workArea?.areaName}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{formatNumber(task.workItem.plannedWork)}
|
||||||
|
</td>
|
||||||
|
<td>{`${formatNumber(task.completedTask)} / ${formatNumber(task.plannedTask)}`}</td>
|
||||||
|
<td>{formatUTCToLocalTime(task.assignmentDate)}</td>
|
||||||
|
<td className="text-center">{renderTeamMembers(task, idx)}</td>
|
||||||
|
<td className="text-center">
|
||||||
|
<div className="d-flex justify-content-end gap-2">
|
||||||
|
{ReportTaskRights && !task.reportedDate && (
|
||||||
|
<button className="btn btn-xs btn-primary" onClick={() => openModal("report", task)}>
|
||||||
|
Report
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{ApprovedTaskRights && task.reportedDate && !task.approvedBy && (
|
||||||
|
<button
|
||||||
|
className="btn btn-xs btn-warning"
|
||||||
|
onClick={() => openModal("comments", { task, isActionAllow: true })}
|
||||||
|
>
|
||||||
|
QC
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
className="btn btn-xs btn-primary"
|
||||||
|
onClick={() => openModal("comments", { task, isActionAllow: false })}
|
||||||
|
>
|
||||||
|
Comment
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
data?.data?.length > 0 && (
|
||||||
|
<Pagination
|
||||||
|
currentPage={currentPage}
|
||||||
|
totalPages={data.totalPages}
|
||||||
|
onPageChange={paginate}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div >
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TaskReportList;
|
||||||
62
src/components/DailyProgressRport/TaskRepprtListSkeleton.jsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
|
||||||
|
const SkeletonLine = ({ height = 20, width = "100%", className = "" }) => (
|
||||||
|
<div
|
||||||
|
className={`skeleton mb-2 ${className}`}
|
||||||
|
style={{
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
);
|
||||||
|
export const TaskReportListSkeleton = () => {
|
||||||
|
const skeletonRows = 8; // Number of placeholder rows
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<table className="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Activity</th>
|
||||||
|
<th>Assigned</th>
|
||||||
|
<th>Completed</th>
|
||||||
|
<th>Assign On</th>
|
||||||
|
<th>Team</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{[...Array(skeletonRows)].map((_, idx) => (
|
||||||
|
<tr key={idx}>
|
||||||
|
<td>
|
||||||
|
<SkeletonLine height={16} width="70%" />
|
||||||
|
<SkeletonLine height={12} width="50%" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<SkeletonLine height={16} width="60%" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<SkeletonLine height={16} width="60%" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<SkeletonLine height={16} width="80%" />
|
||||||
|
</td>
|
||||||
|
<td className="text-center">
|
||||||
|
<div className="d-flex justify-content-center gap-1">
|
||||||
|
{[...Array(3)].map((_, i) => (
|
||||||
|
<SkeletonLine key={i} height={24} width={24} className="rounded-circle" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div className="d-flex justify-content-end gap-2">
|
||||||
|
<SkeletonLine height={24} width="60px" />
|
||||||
|
<SkeletonLine height={24} width="60px" />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
17
src/components/DailyProgressRport/TaskRportScheam.jsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const TaskReportFilterSchema = z.object({
|
||||||
|
buildingIds: z.array(z.string()).optional(),
|
||||||
|
floorIds: z.array(z.string()).optional(),
|
||||||
|
activityIds: z.array(z.string()).optional(),
|
||||||
|
dateFrom: z.string().optional(),
|
||||||
|
dateTo: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const TaskReportDefaultValue = {
|
||||||
|
buildingIds:[],
|
||||||
|
floorIds:[],
|
||||||
|
activityIds:[],
|
||||||
|
dateFrom:null,
|
||||||
|
dateTo:null
|
||||||
|
}
|
||||||
@ -1,194 +1,194 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
// import React, { useState, useEffect } from "react";
|
||||||
import LineChart from "../Charts/LineChart";
|
// import LineChart from "../Charts/LineChart";
|
||||||
import { useProjects } from "../../hooks/useProjects";
|
// import { useProjects } from "../../hooks/useProjects";
|
||||||
import { useDashboard_ActivityData } from "../../hooks/useDashboard_Data";
|
// import { useDashboard_ActivityData } from "../../hooks/useDashboard_Data";
|
||||||
import ApexChart from "../Charts/Circlechart";
|
// import ApexChart from "../Charts/Circlechart";
|
||||||
|
|
||||||
const LOCAL_STORAGE_PROJECT_KEY = "selectedActivityProjectId";
|
// const LOCAL_STORAGE_PROJECT_KEY = "selectedActivityProjectId";
|
||||||
|
|
||||||
const Activity = () => {
|
// const Activity = () => {
|
||||||
const { projects } = useProjects();
|
// const { projects } = useProjects();
|
||||||
const today = new Date().toISOString().split("T")[0]; // Format: YYYY-MM-DD
|
// const today = new Date().toISOString().split("T")[0]; // Format: YYYY-MM-DD
|
||||||
const [selectedDate, setSelectedDate] = useState(today);
|
// const [selectedDate, setSelectedDate] = useState(today);
|
||||||
const storedProjectId = localStorage.getItem(LOCAL_STORAGE_PROJECT_KEY);
|
// const storedProjectId = localStorage.getItem(LOCAL_STORAGE_PROJECT_KEY);
|
||||||
const initialProjectId = storedProjectId || "all";
|
// const initialProjectId = storedProjectId || "all";
|
||||||
const [selectedProjectId, setSelectedProjectId] = useState(initialProjectId);
|
// const [selectedProjectId, setSelectedProjectId] = useState(initialProjectId);
|
||||||
const [displayedProjectName, setDisplayedProjectName] = useState("Select Project");
|
// const [displayedProjectName, setDisplayedProjectName] = useState("Select Project");
|
||||||
const [activeTab, setActiveTab] = useState("all");
|
// const [activeTab, setActiveTab] = useState("all");
|
||||||
|
|
||||||
const { dashboard_Activitydata: ActivityData, isLoading, error: isError } =
|
// const { dashboard_Activitydata: ActivityData, isLoading, error: isError } =
|
||||||
useDashboard_ActivityData(selectedDate, selectedProjectId);
|
// useDashboard_ActivityData(selectedDate, selectedProjectId);
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
if (selectedProjectId === "all") {
|
// if (selectedProjectId === "all") {
|
||||||
setDisplayedProjectName("All Projects");
|
// setDisplayedProjectName("All Projects");
|
||||||
} else if (projects) {
|
// } else if (projects) {
|
||||||
const foundProject = projects.find((p) => p.id === selectedProjectId);
|
// const foundProject = projects.find((p) => p.id === selectedProjectId);
|
||||||
setDisplayedProjectName(foundProject ? foundProject.name : "Select Project");
|
// setDisplayedProjectName(foundProject ? foundProject.name : "Select Project");
|
||||||
} else {
|
// } else {
|
||||||
setDisplayedProjectName("Select Project");
|
// setDisplayedProjectName("Select Project");
|
||||||
}
|
// }
|
||||||
|
|
||||||
localStorage.setItem(LOCAL_STORAGE_PROJECT_KEY, selectedProjectId);
|
// localStorage.setItem(LOCAL_STORAGE_PROJECT_KEY, selectedProjectId);
|
||||||
}, [selectedProjectId, projects]);
|
// }, [selectedProjectId, projects]);
|
||||||
|
|
||||||
const handleProjectSelect = (projectId) => {
|
// const handleProjectSelect = (projectId) => {
|
||||||
setSelectedProjectId(projectId);
|
// setSelectedProjectId(projectId);
|
||||||
};
|
// };
|
||||||
|
|
||||||
const handleDateChange = (e) => {
|
// const handleDateChange = (e) => {
|
||||||
setSelectedDate(e.target.value);
|
// setSelectedDate(e.target.value);
|
||||||
};
|
// };
|
||||||
|
|
||||||
return (
|
// return (
|
||||||
<div className="card h-100">
|
// <div className="card h-100">
|
||||||
<div className="card-header">
|
// <div className="card-header">
|
||||||
<div className="d-flex flex-wrap justify-content-between align-items-center mb-0">
|
// <div className="d-flex flex-wrap justify-content-between align-items-center mb-0">
|
||||||
<div className="card-title mb-0 text-start">
|
// <div className="card-title mb-0 text-start">
|
||||||
<h5 className="mb-1">Activity</h5>
|
// <h5 className="mb-1">Activity</h5>
|
||||||
<p className="card-subtitle">Activity Progress Chart</p>
|
// <p className="card-subtitle text-primary">Activity Progress Chart</p>
|
||||||
</div>
|
// </div>
|
||||||
|
|
||||||
<div className="btn-group">
|
// <div className="btn-group">
|
||||||
<button
|
// <button
|
||||||
className="btn btn-outline-primary btn-sm dropdown-toggle"
|
// className="btn btn-outline-primary btn-sm dropdown-toggle"
|
||||||
type="button"
|
// type="button"
|
||||||
data-bs-toggle="dropdown"
|
// data-bs-toggle="dropdown"
|
||||||
aria-expanded="false"
|
// aria-expanded="false"
|
||||||
>
|
// >
|
||||||
{displayedProjectName}
|
// {displayedProjectName}
|
||||||
</button>
|
// </button>
|
||||||
<ul className="dropdown-menu">
|
// <ul className="dropdown-menu">
|
||||||
<li>
|
// <li>
|
||||||
<button className="dropdown-item" onClick={() => handleProjectSelect("all")}>
|
// <button className="dropdown-item" onClick={() => handleProjectSelect("all")}>
|
||||||
All Projects
|
// All Projects
|
||||||
</button>
|
// </button>
|
||||||
</li>
|
// </li>
|
||||||
{projects?.map((project) => (
|
// {projects?.map((project) => (
|
||||||
<li key={project.id}>
|
// <li key={project.id}>
|
||||||
<button
|
// <button
|
||||||
className="dropdown-item"
|
// className="dropdown-item"
|
||||||
onClick={() => handleProjectSelect(project.id)}
|
// onClick={() => handleProjectSelect(project.id)}
|
||||||
>
|
// >
|
||||||
{project.name}
|
// {project.name}
|
||||||
</button>
|
// </button>
|
||||||
</li>
|
// </li>
|
||||||
))}
|
// ))}
|
||||||
</ul>
|
// </ul>
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
|
|
||||||
{/* ✅ Date Picker Aligned Left with Padding */}
|
// {/* ✅ Date Picker Aligned Left with Padding */}
|
||||||
<div className="d-flex justify-content-start ps-3 mb-3">
|
// <div className="d-flex justify-content-start ps-3 mb-3">
|
||||||
<div style={{ width: "150px" }}>
|
// <div style={{ width: "150px" }}>
|
||||||
<input
|
// <input
|
||||||
type="date"
|
// type="date"
|
||||||
className="form-control"
|
// className="form-control"
|
||||||
value={selectedDate}
|
// value={selectedDate}
|
||||||
onChange={handleDateChange}
|
// onChange={handleDateChange}
|
||||||
/>
|
// />
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
|
|
||||||
{/* Tabs */}
|
// {/* Tabs */}
|
||||||
<ul className="nav nav-tabs " role="tablist">
|
// <ul className="nav nav-tabs " role="tablist">
|
||||||
<li className="nav-item">
|
// <li className="nav-item">
|
||||||
<button
|
// <button
|
||||||
type="button"
|
// type="button"
|
||||||
className={`nav-link ${activeTab === "all" ? "active" : ""}`}
|
// className={`nav-link ${activeTab === "all" ? "active" : ""}`}
|
||||||
onClick={() => setActiveTab("all")}
|
// onClick={() => setActiveTab("all")}
|
||||||
data-bs-toggle="tab"
|
// data-bs-toggle="tab"
|
||||||
>
|
// >
|
||||||
Summary
|
// Summary
|
||||||
</button>
|
// </button>
|
||||||
</li>
|
// </li>
|
||||||
<li className="nav-item">
|
// <li className="nav-item">
|
||||||
<button
|
// <button
|
||||||
type="button"
|
// type="button"
|
||||||
className={`nav-link ${activeTab === "logs" ? "active" : ""}`}
|
// className={`nav-link ${activeTab === "logs" ? "active" : ""}`}
|
||||||
onClick={() => setActiveTab("logs")}
|
// onClick={() => setActiveTab("logs")}
|
||||||
data-bs-toggle="tab"
|
// data-bs-toggle="tab"
|
||||||
>
|
// >
|
||||||
Details
|
// Details
|
||||||
</button>
|
// </button>
|
||||||
</li>
|
// </li>
|
||||||
</ul>
|
// </ul>
|
||||||
|
|
||||||
<div className="card-body">
|
// <div className="card-body">
|
||||||
{activeTab === "all" && (
|
// {activeTab === "all" && (
|
||||||
<div className="row justify-content-between">
|
// <div className="row justify-content-between">
|
||||||
<div className="col-md-6 d-flex flex-column align-items-center text-center mb-4">
|
// <div className="col-md-6 d-flex flex-column align-items-center text-center mb-4">
|
||||||
{isLoading ? (
|
// {isLoading ? (
|
||||||
<p>Loading activity data...</p>
|
// <p>Loading activity data...</p>
|
||||||
) : isError ? (
|
// ) : isError ? (
|
||||||
<p>No data available.</p>
|
// <p>No data available.</p>
|
||||||
) : (
|
// ) : (
|
||||||
ActivityData && (
|
// ActivityData && (
|
||||||
<>
|
// <>
|
||||||
<h5 className="fw-bold mb-0 text-start w-80">
|
// <h5 className="fw-bold mb-0 text-start w-80">
|
||||||
<i className="bx bx-task text-info"></i> Allocated Task
|
// <i className="bx bx-task text-info"></i> Allocated Task
|
||||||
</h5>
|
// </h5>
|
||||||
<h4 className="mb-0 fw-bold">
|
// <h4 className="mb-0 fw-bold">
|
||||||
{ActivityData.totalCompletedWork?.toLocaleString()}/
|
// {ActivityData.totalCompletedWork?.toLocaleString()}/
|
||||||
{ActivityData.totalPlannedWork?.toLocaleString()}
|
// {ActivityData.totalPlannedWork?.toLocaleString()}
|
||||||
</h4>
|
// </h4>
|
||||||
<small className="text-muted">Completed / Assigned</small>
|
// <small className="text-muted">Completed / Assigned</small>
|
||||||
<div style={{ maxWidth: "180px" }}>
|
// <div style={{ maxWidth: "180px" }}>
|
||||||
<ApexChart />
|
// <ApexChart />
|
||||||
</div>
|
// </div>
|
||||||
</>
|
// </>
|
||||||
)
|
// )
|
||||||
)}
|
// )}
|
||||||
</div>
|
// </div>
|
||||||
|
|
||||||
<div className="col-md-6 d-flex flex-column align-items-center text-center mb-4">
|
// <div className="col-md-6 d-flex flex-column align-items-center text-center mb-4">
|
||||||
{!isLoading && !isError && ActivityData && (
|
// {!isLoading && !isError && ActivityData && (
|
||||||
<>
|
// <>
|
||||||
<h5 className="fw-bold mb-0 text-start w-110">
|
// <h5 className="fw-bold mb-0 text-start w-110">
|
||||||
<i className="bx bx-task text-info"></i> Activities
|
// <i className="bx bx-task text-info"></i> Activities
|
||||||
</h5>
|
// </h5>
|
||||||
<h4 className="mb-0 fw-bold">
|
// <h4 className="mb-0 fw-bold">
|
||||||
{ActivityData.totalCompletedWork?.toLocaleString()}/
|
// {ActivityData.totalCompletedWork?.toLocaleString()}/
|
||||||
{ActivityData.totalPlannedWork?.toLocaleString()}
|
// {ActivityData.totalPlannedWork?.toLocaleString()}
|
||||||
</h4>
|
// </h4>
|
||||||
<small className="text-muted ">Pending / Assigned</small>
|
// <small className="text-muted ">Pending / Assigned</small>
|
||||||
<div style={{ maxWidth: "180px" }}>
|
// <div style={{ maxWidth: "180px" }}>
|
||||||
<ApexChart />
|
// <ApexChart />
|
||||||
</div>
|
// </div>
|
||||||
</>
|
// </>
|
||||||
)}
|
// )}
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
)}
|
// )}
|
||||||
|
|
||||||
{activeTab === "logs" && (
|
// {activeTab === "logs" && (
|
||||||
<div className="table-responsive">
|
// <div className="table-responsive">
|
||||||
<table className="table table-bordered table-hover">
|
// <table className="table table-bordered table-hover">
|
||||||
<thead>
|
// <thead>
|
||||||
<tr>
|
// <tr>
|
||||||
<th>Activity / Location</th>
|
// <th>Activity / Location</th>
|
||||||
<th>Assigned / Completed</th>
|
// <th>Assigned / Completed</th>
|
||||||
</tr>
|
// </tr>
|
||||||
</thead>
|
// </thead>
|
||||||
<tbody>
|
// <tbody>
|
||||||
{[{
|
// {[{
|
||||||
activity: "Code Review / Remote",
|
// activity: "Code Review / Remote",
|
||||||
assignedToday: 3,
|
// assignedToday: 3,
|
||||||
completed: 2
|
// completed: 2
|
||||||
}].map((log, index) => (
|
// }].map((log, index) => (
|
||||||
<tr key={index}>
|
// <tr key={index}>
|
||||||
<td>{log.activity}</td>
|
// <td>{log.activity}</td>
|
||||||
<td>{log.assignedToday} / {log.completed}</td>
|
// <td>{log.assignedToday} / {log.completed}</td>
|
||||||
</tr>
|
// </tr>
|
||||||
))}
|
// ))}
|
||||||
</tbody>
|
// </tbody>
|
||||||
</table>
|
// </table>
|
||||||
</div>
|
// </div>
|
||||||
)}
|
// )}
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
);
|
// );
|
||||||
};
|
// };
|
||||||
|
|
||||||
export default Activity;
|
// export default Activity;
|
||||||
|
|||||||
@ -1,21 +1,16 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useMemo } from "react";
|
||||||
import LineChart from "../Charts/LineChart";
|
import ApexChart from "../Charts/Circle";
|
||||||
import { useProjects } from "../../hooks/useProjects";
|
import { useProjects } from "../../hooks/useProjects";
|
||||||
import { useDashboard_AttendanceData } from "../../hooks/useDashboard_Data";
|
import { useDashboard_AttendanceData } from "../../hooks/useDashboard_Data";
|
||||||
import ApexChart from "../Charts/Circle";
|
import { useSelectedProject } from "../../hooks/useSelectedProject"; // ✅ your custom hook
|
||||||
|
|
||||||
const LOCAL_STORAGE_PROJECT_KEY = "selectedAttendanceProjectId";
|
|
||||||
|
|
||||||
const Attendance = () => {
|
const Attendance = () => {
|
||||||
const { projects } = useProjects();
|
const { projects } = useProjects();
|
||||||
const today = new Date().toISOString().split("T")[0]; // Format: YYYY-MM-DD
|
const today = new Date().toISOString().split("T")[0]; // YYYY-MM-DD
|
||||||
const [selectedDate, setSelectedDate] = useState(today);
|
const [selectedDate, setSelectedDate] = useState(today);
|
||||||
const storedProjectId = localStorage.getItem(LOCAL_STORAGE_PROJECT_KEY);
|
|
||||||
const initialProjectId = storedProjectId || "all";
|
// central project selection hook
|
||||||
const [selectedProjectId, setSelectedProjectId] = useState(initialProjectId);
|
const selectedProjectId = useSelectedProject()
|
||||||
const [displayedProjectName, setDisplayedProjectName] =
|
|
||||||
useState("Select Project");
|
|
||||||
const [activeTab, setActiveTab] = useState("Summary");
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
dashboard_Attendancedata: AttendanceData,
|
dashboard_Attendancedata: AttendanceData,
|
||||||
@ -23,38 +18,24 @@ const Attendance = () => {
|
|||||||
error: isError,
|
error: isError,
|
||||||
} = useDashboard_AttendanceData(selectedDate, selectedProjectId);
|
} = useDashboard_AttendanceData(selectedDate, selectedProjectId);
|
||||||
|
|
||||||
useEffect(() => {
|
// project name derived once
|
||||||
if (selectedProjectId === "all") {
|
const displayedProjectName = useMemo(() => {
|
||||||
setDisplayedProjectName("All Projects");
|
if (selectedProjectId === "all") return "All Projects";
|
||||||
} else if (projects) {
|
const found = projects?.find((p) => p.id === selectedProjectId);
|
||||||
const foundProject = projects.find((p) => p.id === selectedProjectId);
|
return found?.name || "Select Project";
|
||||||
setDisplayedProjectName(
|
|
||||||
foundProject ? foundProject.name : "Select Project"
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
setDisplayedProjectName("Select Project");
|
|
||||||
}
|
|
||||||
|
|
||||||
localStorage.setItem(LOCAL_STORAGE_PROJECT_KEY, selectedProjectId);
|
|
||||||
}, [selectedProjectId, projects]);
|
}, [selectedProjectId, projects]);
|
||||||
|
|
||||||
const handleProjectSelect = (projectId) => {
|
|
||||||
setSelectedProjectId(projectId);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDateChange = (e) => {
|
|
||||||
setSelectedDate(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card h-100">
|
<div className="card h-100">
|
||||||
<div className="card-header mb-1 pb-0 ">
|
{/* Header */}
|
||||||
<div className="d-flex flex-wrap justify-content-between align-items-center mb-0 pb-0 ">
|
<div className="card-header mb-1 pb-0">
|
||||||
|
<div className="d-flex flex-wrap justify-content-between align-items-center">
|
||||||
<div className="card-title mb-0 text-start">
|
<div className="card-title mb-0 text-start">
|
||||||
<h5 className="mb-1">Attendance</h5>
|
<h5 className="mb-1">Attendance</h5>
|
||||||
<p className="card-subtitle">Daily Attendance Data</p>
|
<p className="card-subtitle">Daily Attendance Data</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Project Dropdown */}
|
||||||
<div className="btn-group">
|
<div className="btn-group">
|
||||||
<button
|
<button
|
||||||
className="btn btn-outline-primary btn-sm dropdown-toggle"
|
className="btn btn-outline-primary btn-sm dropdown-toggle"
|
||||||
@ -68,7 +49,7 @@ const Attendance = () => {
|
|||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
className="dropdown-item"
|
className="dropdown-item"
|
||||||
onClick={() => handleProjectSelect("all")}
|
onClick={() => setSelectedProjectId("all")}
|
||||||
>
|
>
|
||||||
All Projects
|
All Projects
|
||||||
</button>
|
</button>
|
||||||
@ -77,7 +58,7 @@ const Attendance = () => {
|
|||||||
<li key={project.id}>
|
<li key={project.id}>
|
||||||
<button
|
<button
|
||||||
className="dropdown-item"
|
className="dropdown-item"
|
||||||
onClick={() => handleProjectSelect(project.id)}
|
onClick={() => setSelectedProjectId(project.id)}
|
||||||
>
|
>
|
||||||
{project.name}
|
{project.name}
|
||||||
</button>
|
</button>
|
||||||
@ -88,18 +69,14 @@ const Attendance = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="d-flex flex-wrap justify-content-between align-items-center mb-0 mt-0 me-5 ms-5">
|
{/* Tabs + Date Picker */}
|
||||||
{/* Tabs */}
|
<div className="d-flex flex-wrap justify-content-between align-items-center me-5 ms-5">
|
||||||
<div>
|
<ul className="nav nav-tabs">
|
||||||
<ul className="nav nav-tabs " role="tablist">
|
|
||||||
<li className="nav-item">
|
<li className="nav-item">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`nav-link ${
|
className={`nav-link ${AttendanceData?.activeTab === "Summary" ? "active" : ""}`}
|
||||||
activeTab === "Summary" ? "active" : ""
|
onClick={() => (AttendanceData.activeTab = "Summary")}
|
||||||
}`}
|
|
||||||
onClick={() => setActiveTab("Summary")}
|
|
||||||
data-bs-toggle="tab"
|
|
||||||
>
|
>
|
||||||
Summary
|
Summary
|
||||||
</button>
|
</button>
|
||||||
@ -107,33 +84,28 @@ const Attendance = () => {
|
|||||||
<li className="nav-item">
|
<li className="nav-item">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`nav-link ${
|
className={`nav-link ${AttendanceData?.activeTab === "Details" ? "active" : ""}`}
|
||||||
activeTab === "Details" ? "active" : ""
|
onClick={() => (AttendanceData.activeTab = "Details")}
|
||||||
}`}
|
|
||||||
onClick={() => setActiveTab("Details")}
|
|
||||||
data-bs-toggle="tab"
|
|
||||||
>
|
>
|
||||||
Details
|
Details
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
<div className="ps-6 mb-3">
|
||||||
{/* ✅ Date Picker Aligned Left with Padding */}
|
|
||||||
<div className="ps-6 mb-3 mt-0">
|
|
||||||
<div style={{ width: "120px" }}>
|
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
className="form-control p-1"
|
className="form-control p-1"
|
||||||
// style={{ fontSize: "1rem" }}
|
style={{ width: "120px" }}
|
||||||
value={selectedDate}
|
value={selectedDate}
|
||||||
onChange={handleDateChange}
|
onChange={(e) => setSelectedDate(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
{/* Body */}
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
{activeTab === "Summary" && (
|
{/* Summary */}
|
||||||
|
{AttendanceData?.activeTab === "Summary" && (
|
||||||
<div className="row justify-content-center">
|
<div className="row justify-content-center">
|
||||||
<div className="col-12 col-md-6 d-flex flex-column align-items-center text-center mb-4">
|
<div className="col-12 col-md-6 d-flex flex-column align-items-center text-center mb-4">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@ -143,7 +115,7 @@ const Attendance = () => {
|
|||||||
) : (
|
) : (
|
||||||
AttendanceData && (
|
AttendanceData && (
|
||||||
<>
|
<>
|
||||||
<h5 className="fw-bold mb-0 text-center w-100">
|
<h5 className="fw-bold mb-0">
|
||||||
<i className="bx bx-task text-info"></i> Attendance
|
<i className="bx bx-task text-info"></i> Attendance
|
||||||
</h5>
|
</h5>
|
||||||
<h4 className="mb-0 fw-bold">
|
<h4 className="mb-0 fw-bold">
|
||||||
@ -164,11 +136,9 @@ const Attendance = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === "Details" && (
|
{/* Details */}
|
||||||
<div
|
{AttendanceData?.activeTab === "Details" && (
|
||||||
className="table-responsive"
|
<div className="table-responsive" style={{ maxHeight: "300px" }}>
|
||||||
style={{ maxHeight: "300px", overflowY: "auto" }}
|
|
||||||
>
|
|
||||||
<table className="table table-hover mb-0 text-start">
|
<table className="table table-hover mb-0 text-start">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -178,32 +148,17 @@ const Attendance = () => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{AttendanceData?.attendanceTable &&
|
{AttendanceData?.attendanceTable?.length ? (
|
||||||
AttendanceData.attendanceTable.length > 0 ? (
|
AttendanceData.attendanceTable.map((r, i) => (
|
||||||
AttendanceData.attendanceTable.map((record, index) => (
|
<tr key={i}>
|
||||||
<tr key={index}>
|
<td>{r.firstName} {r.lastName}</td>
|
||||||
<td>
|
<td>{r.inTime ? new Date(r.inTime).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "-"}</td>
|
||||||
{record.firstName} {record.lastName}
|
<td>{r.outTime ? new Date(r.outTime).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "-"}</td>
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{new Date(record.inTime).toLocaleTimeString([], {
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
})}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{new Date(record.outTime).toLocaleTimeString([], {
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
})}
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan="3" className="text-center">
|
<td colSpan="3" className="text-center">No attendance data available</td>
|
||||||
No attendance data available
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@ -99,9 +99,7 @@ const AttendanceOverview = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="bg-white p-4 rounded shadow d-flex flex-column">
|
||||||
className="bg-white p-4 rounded shadow d-flex flex-column"
|
|
||||||
>
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="d-flex justify-content-between align-items-center mb-3">
|
<div className="d-flex justify-content-between align-items-center mb-3">
|
||||||
<div className="card-title mb-0 text-start">
|
<div className="card-title mb-0 text-start">
|
||||||
@ -119,18 +117,22 @@ const AttendanceOverview = () => {
|
|||||||
<option value={30}>Last 30 Days</option>
|
<option value={30}>Last 30 Days</option>
|
||||||
</select>
|
</select>
|
||||||
<button
|
<button
|
||||||
className={`btn btn-sm ${view === "chart" ? "btn-primary" : "btn-outline-primary"}`}
|
className={`btn btn-sm p-1 ${
|
||||||
|
view === "chart" ? "btn-primary" : "btn-outline-primary"
|
||||||
|
}`}
|
||||||
onClick={() => setView("chart")}
|
onClick={() => setView("chart")}
|
||||||
title="Chart View"
|
title="Chart View"
|
||||||
>
|
>
|
||||||
<i className="bx bx-bar-chart-alt-2"></i>
|
<i className="bx bx-bar-chart-alt-2"></i>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className={`btn btn-sm ${view === "table" ? "btn-primary" : "btn-outline-primary"}`}
|
className={`btn btn-sm p-1 ${
|
||||||
|
view === "table" ? "btn-primary" : "btn-outline-primary"
|
||||||
|
}`}
|
||||||
onClick={() => setView("table")}
|
onClick={() => setView("table")}
|
||||||
title="Table View"
|
title="Table View"
|
||||||
>
|
>
|
||||||
<i className="bx bx-task text-success"></i>
|
<i className="bx bx-list-ul fs-5"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -14,11 +14,11 @@ import ProjectCompletionChart from "./ProjectCompletionChart";
|
|||||||
import ProjectProgressChart from "./ProjectProgressChart";
|
import ProjectProgressChart from "./ProjectProgressChart";
|
||||||
import ProjectOverview from "../Project/ProjectOverview";
|
import ProjectOverview from "../Project/ProjectOverview";
|
||||||
import AttendanceOverview from "./AttendanceChart";
|
import AttendanceOverview from "./AttendanceChart";
|
||||||
|
import ExpenseAnalysis from "./ExpenseAnalysis";
|
||||||
|
import ExpenseStatus from "./ExpenseStatus";
|
||||||
|
import ExpenseByProject from "./ExpenseByProject";
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const { projectsCardData } = useDashboardProjectsCardData();
|
|
||||||
const { teamsCardData } = useDashboardTeamsCardData();
|
|
||||||
const { tasksCardData } = useDashboardTasksCardData();
|
|
||||||
|
|
||||||
// Get the selected project ID from Redux store
|
// Get the selected project ID from Redux store
|
||||||
const projectId = useSelector((store) => store.localVariables.projectId);
|
const projectId = useSelector((store) => store.localVariables.projectId);
|
||||||
@ -29,16 +29,16 @@ const Dashboard = () => {
|
|||||||
<div className="row gy-4">
|
<div className="row gy-4">
|
||||||
{isAllProjectsSelected && (
|
{isAllProjectsSelected && (
|
||||||
<div className="col-sm-6 col-lg-4">
|
<div className="col-sm-6 col-lg-4">
|
||||||
<Projects projectsCardData={projectsCardData} />
|
<Projects />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={`${!isAllProjectsSelected ? "col-sm-6 col-lg-6" : "col-sm-6 col-lg-4"}`}>
|
<div className={`${!isAllProjectsSelected ? "col-sm-6 col-lg-6" : "col-sm-6 col-lg-4"}`}>
|
||||||
<Teams teamsCardData={teamsCardData} />
|
<Teams />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={`${!isAllProjectsSelected ? "col-sm-6 col-lg-6" : "col-sm-6 col-lg-4"}`}>
|
<div className={`${!isAllProjectsSelected ? "col-sm-6 col-lg-6" : "col-sm-6 col-lg-4"}`}>
|
||||||
<TasksCard tasksCardData={tasksCardData} />
|
<TasksCard/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isAllProjectsSelected && (
|
{isAllProjectsSelected && (
|
||||||
@ -56,11 +56,25 @@ const Dashboard = () => {
|
|||||||
<div className="col-xxl-6 col-lg-6">
|
<div className="col-xxl-6 col-lg-6">
|
||||||
<ProjectProgressChart />
|
<ProjectProgressChart />
|
||||||
</div>
|
</div>
|
||||||
|
<div className="col-12 col-xl-8">
|
||||||
|
<div className="card h-100">
|
||||||
|
<ExpenseAnalysis />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-12 col-xl-4 col-md-6">
|
||||||
|
<div className="card ">
|
||||||
|
<ExpenseStatus />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{!isAllProjectsSelected && (
|
{!isAllProjectsSelected && (
|
||||||
<div className="col-xxl-6 col-lg-6">
|
<div className="col-12 col-md-6 mb-sm-0 mb-4 ">
|
||||||
<AttendanceOverview /> {/* ✅ Removed unnecessary projectId prop */}
|
<AttendanceOverview />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<div className="col-12 col-md-6">
|
||||||
|
<ExpenseByProject />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
110
src/components/Dashboard/DashboardSkeleton.jsx
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const SkeletonLine = ({ height = 20, width = "100%", className = "" }) => (
|
||||||
|
<div
|
||||||
|
className={`skeleton ${className}`}
|
||||||
|
style={{
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
borderRadius: "4px",
|
||||||
|
background: "linear-gradient(90deg, #eee, #f5f5f5, #eee)",
|
||||||
|
backgroundSize: "200% 100%",
|
||||||
|
animation: "skeleton-loading 1.5s infinite",
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const skeletonStyle = `
|
||||||
|
@keyframes skeleton-loading {
|
||||||
|
0% { background-position: 200% 0; }
|
||||||
|
100% { background-position: -200% 0; }
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ProjectCardSkeleton = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Inject animation CSS once */}
|
||||||
|
<style>{skeletonStyle}</style>
|
||||||
|
|
||||||
|
<div className="card p-3 h-100 text-center d-flex justify-content-between">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="d-flex justify-content-start align-items-center mb-3">
|
||||||
|
<h5 className="fw-bold mb-0 ms-2">
|
||||||
|
<i className="rounded-circle bx bx-building-house text-primary"></i>{" "}
|
||||||
|
Projects
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Skeleton body */}
|
||||||
|
<div className="d-flex justify-content-around align-items-start mt-n2 w-100">
|
||||||
|
<div className="text-center">
|
||||||
|
<SkeletonLine height={28} width="60px" className="mx-auto mb-2" />
|
||||||
|
<SkeletonLine height={14} width="40px" className="mx-auto" />
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<SkeletonLine height={28} width="60px" className="mx-auto mb-2" />
|
||||||
|
<SkeletonLine height={14} width="40px" className="mx-auto" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TeamsSkeleton = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<style>{skeletonStyle}</style>
|
||||||
|
|
||||||
|
<div className="card p-3 h-100 text-center d-flex justify-content-between">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="d-flex justify-content-start align-items-center mb-3">
|
||||||
|
<h5 className="fw-bold mb-0 ms-2">
|
||||||
|
<i className="bx bx-group text-warning"></i> Teams
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Skeleton Body */}
|
||||||
|
<div className="d-flex justify-content-around align-items-start mt-n2 w-100">
|
||||||
|
<div className="text-center">
|
||||||
|
<SkeletonLine height={28} width="60px" className="mx-auto mb-2" />
|
||||||
|
<SkeletonLine height={14} width="90px" className="mx-auto" />
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<SkeletonLine height={28} width="60px" className="mx-auto mb-2" />
|
||||||
|
<SkeletonLine height={14} width="70px" className="mx-auto" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const TasksSkeleton = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<style>{skeletonStyle}</style>
|
||||||
|
|
||||||
|
<div className="card p-3 h-100 text-center d-flex justify-content-between">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="d-flex justify-content-start align-items-center mb-3">
|
||||||
|
<h5 className="fw-bold mb-0 ms-2">
|
||||||
|
<i className="bx bx-task text-success"></i> Tasks
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Skeleton Body */}
|
||||||
|
<div className="d-flex justify-content-around align-items-start mt-n2 w-100">
|
||||||
|
<div className="text-center">
|
||||||
|
<SkeletonLine height={28} width="60px" className="mx-auto mb-2" />
|
||||||
|
<SkeletonLine height={14} width="70px" className="mx-auto" />
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<SkeletonLine height={28} width="60px" className="mx-auto mb-2" />
|
||||||
|
<SkeletonLine height={14} width="90px" className="mx-auto" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
167
src/components/Dashboard/ExpenseAnalysis.jsx
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
|
import Chart from "react-apexcharts";
|
||||||
|
import { useExpenseAnalysis } from "../../hooks/useDashboard_Data";
|
||||||
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
|
import { DateRangePicker1 } from "../common/DateRangePicker";
|
||||||
|
import { FormProvider, useForm } from "react-hook-form";
|
||||||
|
import { formatCurrency, localToUtc } from "../../utils/appUtils";
|
||||||
|
import { useProjectName } from "../../hooks/useProjects";
|
||||||
|
|
||||||
|
const ExpenseAnalysis = () => {
|
||||||
|
const projectId = useSelectedProject();
|
||||||
|
const [projectName, setProjectName] = useState("All Project");
|
||||||
|
const { projectNames, loading } = useProjectName();
|
||||||
|
|
||||||
|
const methods = useForm({
|
||||||
|
defaultValues: { startDate: "", endDate: "" },
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (projectId && projectNames?.length) {
|
||||||
|
const project = projectNames.find((p) => p.id === projectId);
|
||||||
|
setProjectName(project?.name || "All Project");
|
||||||
|
} else {
|
||||||
|
setProjectName("All Project");
|
||||||
|
}
|
||||||
|
}, [projectNames, projectId]);
|
||||||
|
|
||||||
|
const { watch } = methods;
|
||||||
|
const [startDate, endDate] = watch(["startDate", "endDate"]);
|
||||||
|
|
||||||
|
const { data, isLoading, isError, error, isFetching } = useExpenseAnalysis(
|
||||||
|
projectId,
|
||||||
|
startDate ? localToUtc(startDate) : null,
|
||||||
|
endDate ? localToUtc(endDate) : null
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isError) return <div>{error.message}</div>;
|
||||||
|
|
||||||
|
const report = data?.report ?? [];
|
||||||
|
const { labels, series, total } = useMemo(() => {
|
||||||
|
const labels = report.map((item) => item.projectName);
|
||||||
|
const series = report.map((item) => item.totalApprovedAmount || 0);
|
||||||
|
const total = formatCurrency(data?.totalAmount || 0);
|
||||||
|
return { labels, series, total };
|
||||||
|
}, [report, data?.totalAmount]);
|
||||||
|
|
||||||
|
const donutOptions = {
|
||||||
|
chart: { type: "donut" },
|
||||||
|
labels,
|
||||||
|
legend: { show: false },
|
||||||
|
dataLabels: { enabled: true, formatter: (val) => `${val.toFixed(0)}%` },
|
||||||
|
colors: ["#7367F0", "#28C76F", "#FF9F43", "#EA5455", "#00CFE8", "#FF78B8"],
|
||||||
|
plotOptions: {
|
||||||
|
pie: {
|
||||||
|
donut: {
|
||||||
|
size: "70%",
|
||||||
|
labels: {
|
||||||
|
show: true,
|
||||||
|
total: {
|
||||||
|
show: true,
|
||||||
|
label: "Total",
|
||||||
|
fontSize: "16px",
|
||||||
|
formatter: () => `${total}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
responsive: [
|
||||||
|
{
|
||||||
|
breakpoint: 576, // mobile breakpoint
|
||||||
|
options: {
|
||||||
|
chart: { width: "100%" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
|
||||||
|
<div className="card-header d-flex flex-column flex-sm-row justify-content-between align-items-start align-items-sm-center gap-2">
|
||||||
|
<div className="text-start w-100">
|
||||||
|
<h5 className="mb-1 card-title">Expense Breakdown</h5>
|
||||||
|
{/* <p className="card-subtitle mb-0">Category Wise Expense Breakdown</p> */}
|
||||||
|
<p className="card-subtitle m-0">{projectName}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-start text-sm-end w-75">
|
||||||
|
<FormProvider {...methods}>
|
||||||
|
<DateRangePicker1 />
|
||||||
|
</FormProvider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Card body */}
|
||||||
|
<div className="card-body position-relative">
|
||||||
|
{isLoading && (
|
||||||
|
<div
|
||||||
|
className="d-flex justify-content-center align-items-center"
|
||||||
|
style={{ height: "200px" }}
|
||||||
|
>
|
||||||
|
<span>Loading...</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isLoading && report.length === 0 && (
|
||||||
|
<div className="text-center py-5 text-muted">No data found</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isLoading && report.length > 0 && (
|
||||||
|
<>
|
||||||
|
{isFetching && (
|
||||||
|
<div className="position-absolute top-0 start-0 w-100 h-100 d-flex justify-content-center align-items-center bg-white bg-opacity-75">
|
||||||
|
<span>Loading...</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="d-flex justify-content-center mb-3">
|
||||||
|
<Chart
|
||||||
|
options={donutOptions}
|
||||||
|
series={series}
|
||||||
|
type="donut"
|
||||||
|
width="100%"
|
||||||
|
height={320}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-2 w-100">
|
||||||
|
<div className="row g-2">
|
||||||
|
{report.map((item, idx) => (
|
||||||
|
<div
|
||||||
|
className="col-12 col-sm-6 d-flex align-items-start"
|
||||||
|
key={idx}
|
||||||
|
>
|
||||||
|
<div className="avatar me-2">
|
||||||
|
<span
|
||||||
|
className="avatar-initial rounded-2"
|
||||||
|
style={{
|
||||||
|
backgroundColor:
|
||||||
|
donutOptions.colors[idx % donutOptions.colors.length],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className="bx bx-receipt fs-4"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="d-flex flex-column gap-1 text-start">
|
||||||
|
<small className="fw-semibold">{item.projectName}</small>
|
||||||
|
<span className="fw-semibold text-muted ms-1">
|
||||||
|
{formatCurrency(item.totalApprovedAmount)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Header */}
|
||||||
|
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExpenseAnalysis;
|
||||||
178
src/components/Dashboard/ExpenseByProject.jsx
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import Chart from "react-apexcharts";
|
||||||
|
import { useExpenseType } from "../../hooks/masterHook/useMaster";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { useExpenseDataByProject } from "../../hooks/useDashboard_Data";
|
||||||
|
import { formatCurrency } from "../../utils/appUtils";
|
||||||
|
import { formatDate_DayMonth } from "../../utils/dateUtils";
|
||||||
|
import { useProjectName } from "../../hooks/useProjects";
|
||||||
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
|
|
||||||
|
const ExpenseByProject = () => {
|
||||||
|
const projectId = useSelector((store) => store.localVariables.projectId);
|
||||||
|
const [projectName, setProjectName] = useState("All Project");
|
||||||
|
const [range, setRange] = useState("12M");
|
||||||
|
const { projectNames, loading } = useProjectName();
|
||||||
|
const [selectedType, setSelectedType] = useState("");
|
||||||
|
const [viewMode, setViewMode] = useState("Category");
|
||||||
|
const [chartData, setChartData] = useState({ categories: [], data: [] });
|
||||||
|
const selectedProject = useSelectedProject();
|
||||||
|
|
||||||
|
const { ExpenseTypes, loading: typeLoading } = useExpenseType();
|
||||||
|
|
||||||
|
const { data: expenseApiData, isLoading } = useExpenseDataByProject(
|
||||||
|
projectId,
|
||||||
|
selectedType,
|
||||||
|
range === "All" ? null : parseInt(range)
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedProject && projectNames?.length) {
|
||||||
|
const project = projectNames.find((p) => p.id === selectedProject);
|
||||||
|
setProjectName(project?.name || "All Project");
|
||||||
|
} else {
|
||||||
|
setProjectName("All Project");
|
||||||
|
}
|
||||||
|
}, [projectNames, selectedProject]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (expenseApiData) {
|
||||||
|
const categories = expenseApiData.map((item) =>
|
||||||
|
formatDate_DayMonth(item.monthName, item.year)
|
||||||
|
);
|
||||||
|
const data = expenseApiData.map((item) => item.total);
|
||||||
|
setChartData({ categories, data });
|
||||||
|
} else {
|
||||||
|
setChartData({ categories: [], data: [] });
|
||||||
|
}
|
||||||
|
}, [expenseApiData]);
|
||||||
|
|
||||||
|
const getSelectedTypeName = () => {
|
||||||
|
if (!selectedType) return "All Types";
|
||||||
|
const found = ExpenseTypes.find((t) => t.id === selectedType);
|
||||||
|
return found ? found.name : "All Types";
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
chart: { type: "bar", toolbar: { show: false } },
|
||||||
|
plotOptions: {
|
||||||
|
bar: { horizontal: false, columnWidth: "55%", borderRadius: 4 },
|
||||||
|
},
|
||||||
|
dataLabels: { enabled: true, formatter: (val) => formatCurrency(val) },
|
||||||
|
xaxis: {
|
||||||
|
categories: chartData.categories,
|
||||||
|
labels: { style: { fontSize: "12px" }, rotate: -45 },
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
y: {
|
||||||
|
formatter: (val) => `${formatCurrency(val)} (${getSelectedTypeName()})`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
annotations: { xaxis: [{ x: 0, strokeDashArray: 0 }] },
|
||||||
|
fill: { opacity: 1 },
|
||||||
|
colors: ["#2196f3"],
|
||||||
|
};
|
||||||
|
|
||||||
|
const series = [
|
||||||
|
{
|
||||||
|
name: getSelectedTypeName(),
|
||||||
|
data: chartData.data,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="card shadow-sm rounded ">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="card-header">
|
||||||
|
<div className="d-flex justify-content-start align-items-center mb-3 mt-3">
|
||||||
|
<div className="text-start">
|
||||||
|
<h5 className="mb-1 me-6 card-title">Monthly Expense -</h5>
|
||||||
|
<p className="card-subtitle m-0">{projectName}</p>
|
||||||
|
</div>
|
||||||
|
<div className="btn-group mb-4 ms-n8">
|
||||||
|
<button
|
||||||
|
className="btn btn-sm dropdown-toggle fs-5"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
{viewMode}
|
||||||
|
</button>
|
||||||
|
<ul className="dropdown-menu dropdown-menu-end ">
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
setViewMode("Category");
|
||||||
|
setSelectedType("");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Category
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
setViewMode("Project");
|
||||||
|
setSelectedType("");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Project
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Range Buttons + Expense Dropdown */}
|
||||||
|
<div className="d-flex align-items-center flex-wrap ">
|
||||||
|
{["1M", "3M", "6M", "12M", "All"].map((item) => (
|
||||||
|
<button
|
||||||
|
key={item}
|
||||||
|
className={`border-0 px-2 py-1 text-sm rounded ${range === item
|
||||||
|
? "text-white bg-primary"
|
||||||
|
: "text-body bg-transparent"
|
||||||
|
}`}
|
||||||
|
style={{ cursor: "pointer", transition: "all 0.2s ease" }}
|
||||||
|
onClick={() => setRange(item)}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
{viewMode === "Category" && (
|
||||||
|
<select
|
||||||
|
className="form-select form-select-sm ms-auto mb-3 mt-1 mt-sm-0"
|
||||||
|
value={selectedType}
|
||||||
|
onChange={(e) => setSelectedType(e.target.value)}
|
||||||
|
disabled={typeLoading}
|
||||||
|
style={{ maxWidth: "200px" }}
|
||||||
|
>
|
||||||
|
<option value="">All Types</option>
|
||||||
|
{ExpenseTypes.map((type) => (
|
||||||
|
<option key={type.id} value={type.id}>
|
||||||
|
{type.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Chart */}
|
||||||
|
<div className="card-body bg-white text-dark p-3 rounded" style={{ minHeight: "210px" }}>
|
||||||
|
{isLoading ? (
|
||||||
|
<p>Loading chart...</p>
|
||||||
|
) : !expenseApiData || expenseApiData.length === 0 ? (
|
||||||
|
<div className="text-center text-muted py-5">No data found</div>
|
||||||
|
) : (
|
||||||
|
<Chart options={options} series={series} type="bar" height={235} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExpenseByProject;
|
||||||
157
src/components/Dashboard/ExpenseStatus.jsx
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { useExpense } from "../../hooks/useExpense";
|
||||||
|
import { useExpenseStatus } from "../../hooks/useDashboard_Data";
|
||||||
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
|
import { useProjectName } from "../../hooks/useProjects";
|
||||||
|
import { countDigit, formatCurrency } from "../../utils/appUtils";
|
||||||
|
import { EXPENSE_MANAGE, EXPENSE_STATUS } from "../../utils/constants";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
|
|
||||||
|
|
||||||
|
const ExpenseStatus = () => {
|
||||||
|
const [projectName, setProjectName] = useState("All Project");
|
||||||
|
const selectedProject = useSelectedProject();
|
||||||
|
const { projectNames, loading } = useProjectName();
|
||||||
|
const { data, isPending, error } = useExpenseStatus(selectedProject);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const isManageExpense = useHasUserPermission(EXPENSE_MANAGE)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedProject && projectNames?.length) {
|
||||||
|
const project = projectNames.find((p) => p.id === selectedProject);
|
||||||
|
setProjectName(project?.name || "All Project");
|
||||||
|
} else {
|
||||||
|
setProjectName("All Project");
|
||||||
|
}
|
||||||
|
}, [projectNames, selectedProject]);
|
||||||
|
|
||||||
|
const handleNavigate = (status) => {
|
||||||
|
if (selectedProject) {
|
||||||
|
navigate(`/expenses/${status}/${selectedProject}`);
|
||||||
|
} else {
|
||||||
|
navigate(`/expenses/${status}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="card-header d-flex justify-content-between text-start ">
|
||||||
|
<div className="m-0">
|
||||||
|
<h5 className="card-title mb-1">Expense - By Status</h5>
|
||||||
|
<p className="card-subtitle m-0 ">{projectName}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="card-body ">
|
||||||
|
|
||||||
|
<div className="report-list text-start">
|
||||||
|
{[
|
||||||
|
{
|
||||||
|
title: "Pending Payment",
|
||||||
|
count: data?.processPending?.count || 0,
|
||||||
|
amount: data?.processPending?.totalAmount || 0,
|
||||||
|
icon: "bx bx-rupee",
|
||||||
|
iconColor: "text-primary",
|
||||||
|
status: EXPENSE_STATUS.payment_pending,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Pending Approve",
|
||||||
|
count: data?.approvePending?.count || 0,
|
||||||
|
amount: data?.approvePending?.totalAmount || 0,
|
||||||
|
icon: "fa-solid fa-check",
|
||||||
|
iconColor: "text-warning",
|
||||||
|
status: EXPENSE_STATUS.approve_pending,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Pending Review",
|
||||||
|
count: data?.reviewPending?.count || 0,
|
||||||
|
amount: data?.reviewPending?.totalAmount || 0,
|
||||||
|
icon: "bx bx-search-alt-2",
|
||||||
|
iconColor: "text-secondary",
|
||||||
|
status: EXPENSE_STATUS.review_pending,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Draft",
|
||||||
|
count: data?.draft?.count || 0,
|
||||||
|
amount: data?.draft?.totalAmount || 0,
|
||||||
|
icon: "bx bx-file-blank",
|
||||||
|
iconColor: "text-info",
|
||||||
|
status: EXPENSE_STATUS.daft,
|
||||||
|
},
|
||||||
|
].map((item, idx) => (
|
||||||
|
<div
|
||||||
|
key={idx}
|
||||||
|
className="report-list-item rounded-2 mb-4 bg-lighter px-2 py-1 cursor-pointer"
|
||||||
|
onClick={() => handleNavigate(item?.status)}
|
||||||
|
>
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<div className="report-list-icon shadow-xs me-2">
|
||||||
|
<span className="d-inline-flex align-items-center justify-content-center rounded-circle border p-2">
|
||||||
|
<i className={`${item?.icon} ${item?.iconColor} bx-lg`}></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="d-flex justify-content-between align-items-center w-100 flex-wrap gap-2">
|
||||||
|
<div className="d-flex flex-column gap-2">
|
||||||
|
<span className="fw-bold">{item?.title}</span>
|
||||||
|
{item?.amount ? (
|
||||||
|
<small className="mb-0 text-primary">
|
||||||
|
{formatCurrency(item?.amount)}
|
||||||
|
</small>
|
||||||
|
) : (
|
||||||
|
<small className="mb-0 text-primary">{formatCurrency(0)}</small>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<small
|
||||||
|
className={`text-royalblue ${countDigit(item?.count || 0) >= 3 ? "text-xl" : "text-2xl"
|
||||||
|
} text-gray-500`}
|
||||||
|
>
|
||||||
|
{item?.count || 0}
|
||||||
|
</small>
|
||||||
|
<small className="text-muted fs-semibold text-royalblue text-md">
|
||||||
|
<i className="bx bx-chevron-right"></i>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className=" py-0 text-start mb-2">
|
||||||
|
{isManageExpense && (
|
||||||
|
<div
|
||||||
|
className="d-flex justify-content-between align-items-center cursor-pointer"
|
||||||
|
onClick={() => handleNavigate(EXPENSE_STATUS.process_pending)}
|
||||||
|
>
|
||||||
|
<div className="d-block">
|
||||||
|
<span
|
||||||
|
className={`fs-semibold d-block ${countDigit(data?.totalAmount || 0) > 3 ? "text-base" : "text-lg"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Project Spendings:
|
||||||
|
</span>{" "}
|
||||||
|
<small className="d-block text-xxs text-gary-80">
|
||||||
|
(All Processed Payments)
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<div className="d-flex align-items-center gap-2">
|
||||||
|
<span
|
||||||
|
className={`text-end text-royalblue ${countDigit(data?.totalAmount || 0) > 3 ? "text-" : "text-3xl"
|
||||||
|
} text-md`}
|
||||||
|
>
|
||||||
|
{formatCurrency(data?.totalAmount || 0)}
|
||||||
|
</span>
|
||||||
|
<small className="text-muted fs-semibold text-royalblue text-md">
|
||||||
|
<i className="bx bx-chevron-right"></i>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExpenseStatus;
|
||||||
@ -3,7 +3,8 @@ import HorizontalBarChart from "../Charts/HorizontalBarChart";
|
|||||||
import { useProjects } from "../../hooks/useProjects";
|
import { useProjects } from "../../hooks/useProjects";
|
||||||
|
|
||||||
const ProjectCompletionChart = () => {
|
const ProjectCompletionChart = () => {
|
||||||
const { projects, loading } = useProjects();
|
const { data: projects = [], isLoading: loading, isError, error } = useProjects();
|
||||||
|
|
||||||
|
|
||||||
// Bar chart logic
|
// Bar chart logic
|
||||||
const projectNames = projects?.map((p) => p.name) || [];
|
const projectNames = projects?.map((p) => p.name) || [];
|
||||||
@ -11,7 +12,7 @@ const ProjectCompletionChart = () => {
|
|||||||
projects?.map((p) => {
|
projects?.map((p) => {
|
||||||
const completed = p.completedWork || 0;
|
const completed = p.completedWork || 0;
|
||||||
const planned = p.plannedWork || 1;
|
const planned = p.plannedWork || 1;
|
||||||
const percent = (completed / planned) * 100;
|
const percent = planned ? (completed / planned) * 100 : 0;
|
||||||
return Math.min(Math.round(percent), 100);
|
return Math.min(Math.round(percent), 100);
|
||||||
}) || [];
|
}) || [];
|
||||||
|
|
||||||
|
|||||||