Compare commits
334 Commits
Document_M
...
main
Author | SHA1 | Date | |
---|---|---|---|
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": {
|
||||
"version": "0.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
|
||||
"integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
|
||||
"version": "0.3.11",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
||||
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@ -1552,13 +1552,13 @@
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.13.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.13.tgz",
|
||||
"integrity": "sha512-ClsL5nMwKaBRwPcCvH8E7+nU4GxHVx1axNvMZTFHMEfNI7oahimt26P5zjVCRrjiIWj6YFXfE1v3dEp94wLcGQ==",
|
||||
"version": "24.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz",
|
||||
"integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.20.0"
|
||||
"undici-types": "~7.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
@ -1835,9 +1835,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.14.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
||||
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@ -1845,6 +1846,19 @@
|
||||
"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": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
|
||||
@ -2625,9 +2639,9 @@
|
||||
"integrity": "sha512-ZpSAUOZ2Izby7qnZluSrAlGgGQzucmFbN0n64dYzocYxnxV5ufurpj3VgEe4cUp7ir9LmeLxNYo8bVnlM8bQHw=="
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.18.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
|
||||
"integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==",
|
||||
"version": "5.18.3",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
|
||||
"integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@ -2741,9 +2755,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz",
|
||||
"integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==",
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
|
||||
"integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
@ -3138,9 +3152,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz",
|
||||
"integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
|
||||
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@ -5163,9 +5177,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/schema-utils": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz",
|
||||
"integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==",
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
|
||||
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@ -5567,24 +5581,28 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
|
||||
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz",
|
||||
"integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.39.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz",
|
||||
"integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==",
|
||||
"version": "5.44.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz",
|
||||
"integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==",
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.8.2",
|
||||
"acorn": "^8.15.0",
|
||||
"commander": "^2.20.0",
|
||||
"source-map-support": "~0.5.20"
|
||||
},
|
||||
@ -5777,9 +5795,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.20.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
||||
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
|
||||
"version": "7.12.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz",
|
||||
"integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
@ -5907,9 +5925,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/watchpack": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
|
||||
"integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==",
|
||||
"version": "2.4.4",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz",
|
||||
"integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@ -5927,21 +5945,23 @@
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.98.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz",
|
||||
"integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==",
|
||||
"version": "5.101.3",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz",
|
||||
"integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@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/wasm-edit": "^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",
|
||||
"chrome-trace-event": "^1.0.2",
|
||||
"enhanced-resolve": "^5.17.1",
|
||||
"enhanced-resolve": "^5.17.3",
|
||||
"es-module-lexer": "^1.2.1",
|
||||
"eslint-scope": "5.1.1",
|
||||
"events": "^3.2.0",
|
||||
@ -5951,11 +5971,11 @@
|
||||
"loader-runner": "^4.2.0",
|
||||
"mime-types": "^2.1.27",
|
||||
"neo-async": "^2.6.2",
|
||||
"schema-utils": "^4.3.0",
|
||||
"schema-utils": "^4.3.2",
|
||||
"tapable": "^2.1.1",
|
||||
"terser-webpack-plugin": "^5.3.11",
|
||||
"watchpack": "^2.4.1",
|
||||
"webpack-sources": "^3.2.3"
|
||||
"webpack-sources": "^3.3.3"
|
||||
},
|
||||
"bin": {
|
||||
"webpack": "bin/webpack.js"
|
||||
@ -5974,15 +5994,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-sources": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
|
||||
"integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz",
|
||||
"integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"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": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||
|
@ -1,8 +1,282 @@
|
||||
:root,
|
||||
[data-bs-theme="light"] {
|
||||
--bs-nav-link-font-size: 0.7375rem;
|
||||
--bg-border-color :#f8f6f6
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 0.5rem var(--bs-card-cap-padding-x);
|
||||
}
|
||||
.table_header_border {
|
||||
border-bottom:2px solid var(--bs-table-border-color) ;
|
||||
}
|
||||
|
||||
|
||||
.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; }
|
||||
}
|
||||
|
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;
|
||||
}
|
||||
|
||||
.modal-min-h{
|
||||
min-height: 60vh !important;
|
||||
}
|
||||
|
||||
.flex-fill {
|
||||
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 { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||
import { queryClient } from "./layouts/AuthLayout";
|
||||
import ModalProvider from "./ModalProvider";
|
||||
|
||||
|
||||
|
||||
@ -11,6 +12,7 @@ const App = () => {
|
||||
return (
|
||||
<div className="app">
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ModalProvider/>
|
||||
<DireProvider>
|
||||
<AppRoutes />
|
||||
</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",
|
||||
};
|
22
src/ModalProvider.jsx
Normal file
@ -0,0 +1,22 @@
|
||||
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";
|
||||
|
||||
const ModalProvider = () => {
|
||||
const { isOpen, onClose } = useOrganizationModal();
|
||||
const { isOpen: isAuthOpen } = useAuthModal();
|
||||
const {isOpen:isChangePass} = useModal("ChangePassword")
|
||||
|
||||
return (
|
||||
<>
|
||||
{isOpen && <OrganizationModal />}
|
||||
{isAuthOpen && <SwitchTenant />}
|
||||
{isChangePass && <ChangePasswordPage /> }
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModalProvider;
|
@ -12,22 +12,19 @@ import { useQueryClient } from "@tanstack/react-query";
|
||||
import eventBus from "../../services/eventBus";
|
||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
|
||||
const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
||||
const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizationId, }) => {
|
||||
const queryClient = useQueryClient();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const [todayDate, setTodayDate] = useState(new Date());
|
||||
const [ShowPending, setShowPending] = useState(false);
|
||||
// const selectedProject = useSelector(
|
||||
// (store) => store.localVariables.projectId
|
||||
// );
|
||||
const selectedProject = useSelectedProject();
|
||||
const {
|
||||
attendance,
|
||||
loading: attLoading,
|
||||
recall: attrecall,
|
||||
isFetching
|
||||
} = useAttendance(selectedProject);
|
||||
} = useAttendance(selectedProject, organizationId);
|
||||
const filteredAttendance = ShowPending
|
||||
? attendance?.filter(
|
||||
(att) => att?.checkInTime !== null && att?.checkOutTime === null
|
||||
@ -62,12 +59,11 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
||||
const role = item.jobRoleName?.toLowerCase() || "";
|
||||
return (
|
||||
fullName.includes(lowercasedSearchTerm) ||
|
||||
role.includes(lowercasedSearchTerm) // ✅ also search by role
|
||||
role.includes(lowercasedSearchTerm) // also search by role
|
||||
);
|
||||
});
|
||||
}, [group1, group2, searchTerm]);
|
||||
|
||||
|
||||
const { currentPage, totalPages, currentItems, paginate } = usePagination(
|
||||
finalFilteredData,
|
||||
ITEMS_PER_PAGE
|
||||
@ -116,7 +112,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
||||
<>
|
||||
<div
|
||||
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">
|
||||
<strong>Date : {formatUTCToLocalTime(todayDate)}</strong>
|
||||
@ -142,6 +138,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
||||
<tr className="border-top-1">
|
||||
<th colSpan={2}>Name</th>
|
||||
<th>Role</th>
|
||||
<th>Organization</th>
|
||||
<th>
|
||||
<i className="bx bxs-down-arrow-alt text-success"></i>
|
||||
Check-In
|
||||
@ -190,6 +187,8 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
||||
</td>
|
||||
|
||||
<td>{item.jobRoleName}</td>
|
||||
<td>{item.organizationName || "--"}</td>
|
||||
|
||||
<td>
|
||||
{item.checkInTime
|
||||
? convertShortTime(item.checkInTime)
|
||||
@ -213,7 +212,11 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
||||
))}
|
||||
{!attendance && (
|
||||
<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!
|
||||
</td>
|
||||
</tr>
|
||||
@ -221,6 +224,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
{!loading && finalFilteredData.length > ITEMS_PER_PAGE && (
|
||||
<nav aria-label="Page ">
|
||||
<ul className="pagination pagination-sm justify-content-end py-1">
|
||||
|
@ -4,9 +4,12 @@ import Avatar from "../common/Avatar";
|
||||
import { convertShortTime } from "../../utils/dateUtils";
|
||||
import RenderAttendanceStatus from "./RenderAttendanceStatus";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { fetchAttendanceData } from "../../slices/apiSlice/attedanceLogsSlice";
|
||||
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 AttendanceRepository from "../../repositories/AttendanceRepository";
|
||||
import { useAttendancesLogs } from "../../hooks/useAttendance";
|
||||
@ -34,18 +37,13 @@ const usePagination = (data, itemsPerPage) => {
|
||||
};
|
||||
};
|
||||
|
||||
const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
||||
// const selectedProject = useSelector(
|
||||
// (store) => store.localVariables.projectId
|
||||
// );
|
||||
const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
|
||||
const selectedProject = useSelectedProject();
|
||||
const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" });
|
||||
const dispatch = useDispatch();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showPending, setShowPending] = useState(false)
|
||||
|
||||
const [showPending, setShowPending] = useState(false);
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
const [processedData, setProcessedData] = useState([]);
|
||||
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
@ -68,56 +66,32 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
||||
};
|
||||
|
||||
const sortByName = (a, b) => {
|
||||
const nameA = a.firstName.toLowerCase() + a.lastName.toLowerCase();
|
||||
const nameB = b.firstName.toLowerCase() + b.lastName.toLowerCase();
|
||||
return nameA?.localeCompare(nameB);
|
||||
const nameA = (a.firstName + a.lastName).toLowerCase();
|
||||
const nameB = (b.firstName + b.lastName).toLowerCase();
|
||||
return nameA.localeCompare(nameB);
|
||||
};
|
||||
|
||||
const {
|
||||
data = [],
|
||||
isLoading,
|
||||
error,
|
||||
refetch,
|
||||
isFetching,
|
||||
} = useAttendancesLogs(
|
||||
const { data = [], isLoading, error, refetch, isFetching } = useAttendancesLogs(
|
||||
selectedProject,
|
||||
dateRange.startDate,
|
||||
dateRange.endDate
|
||||
dateRange.endDate,
|
||||
organizationId
|
||||
);
|
||||
const filtering = (data) => {
|
||||
|
||||
const processedData = useMemo(() => {
|
||||
const filteredData = showPending
|
||||
? data.filter((item) => item.checkOutTime === null)
|
||||
: data;
|
||||
|
||||
const group1 = filteredData
|
||||
.filter((d) => d.activity === 1 && isSameDay(d.checkInTime))
|
||||
.sort(sortByName);
|
||||
const group2 = filteredData
|
||||
.filter((d) => d.activity === 4 && isSameDay(d.checkOutTime))
|
||||
.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 group1 = filteredData.filter((d) => d.activity === 1 && isSameDay(d.checkInTime)).sort(sortByName);
|
||||
const group2 = filteredData.filter((d) => d.activity === 4 && isSameDay(d.checkOutTime)).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 = [
|
||||
...group1,
|
||||
...group2,
|
||||
...group3,
|
||||
...group4,
|
||||
...group5,
|
||||
...group6,
|
||||
];
|
||||
const sortedList = [...group1, ...group2, ...group3, ...group4, ...group5, ...group6];
|
||||
|
||||
// Group by date
|
||||
const groupedByDate = sortedList.reduce((acc, item) => {
|
||||
const date = (item.checkInTime || item.checkOutTime)?.split("T")[0];
|
||||
if (date) {
|
||||
@ -127,28 +101,17 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const sortedDates = Object.keys(groupedByDate).sort(
|
||||
(a, b) => new Date(b) - new Date(a)
|
||||
);
|
||||
|
||||
const finalData = sortedDates.flatMap((date) => groupedByDate[date]);
|
||||
setProcessedData(finalData);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
filtering(data);
|
||||
const sortedDates = Object.keys(groupedByDate).sort((a, b) => new Date(b) - new Date(a));
|
||||
return sortedDates.flatMap((date) => groupedByDate[date]);
|
||||
}, [data, showPending]);
|
||||
|
||||
// New useEffect to handle search filtering
|
||||
const filteredSearchData = useMemo(() => {
|
||||
if (!searchTerm) {
|
||||
return processedData;
|
||||
}
|
||||
const lowercasedSearchTerm = searchTerm.toLowerCase();
|
||||
return processedData.filter((item) => {
|
||||
const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
|
||||
return fullName.includes(lowercasedSearchTerm);
|
||||
});
|
||||
if (!searchTerm) return processedData;
|
||||
|
||||
const lowercased = searchTerm.toLowerCase();
|
||||
return processedData.filter((item) =>
|
||||
`${item.firstName} ${item.lastName}`.toLowerCase().includes(lowercased)
|
||||
);
|
||||
}, [processedData, searchTerm]);
|
||||
|
||||
const {
|
||||
@ -161,34 +124,27 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
||||
|
||||
useEffect(() => {
|
||||
resetPage();
|
||||
}, [filteredSearchData, resetPage]);
|
||||
}, [filteredSearchData]);
|
||||
|
||||
const handler = useCallback(
|
||||
(msg) => {
|
||||
const { startDate, endDate } = dateRange;
|
||||
const checkIn = msg.response.checkInTime.substring(0, 10);
|
||||
if (
|
||||
selectedProject === msg.projectId &&
|
||||
startDate <= checkIn &&
|
||||
checkIn <= endDate
|
||||
) {
|
||||
|
||||
if (selectedProject === msg.projectId && startDate <= checkIn && checkIn <= endDate) {
|
||||
queryClient.setQueriesData(["attendanceLogs"], (oldData) => {
|
||||
if (!oldData) {
|
||||
queryClient.invalidateQueries({ queryKey: ["attendanceLogs"] });
|
||||
return;
|
||||
}
|
||||
const updatedAttendance = oldData.map((record) =>
|
||||
record.id === msg.response.id
|
||||
? { ...record, ...msg.response }
|
||||
: record
|
||||
return oldData.map((record) =>
|
||||
record.id === msg.response.id ? { ...record, ...msg.response } : record
|
||||
);
|
||||
filtering(updatedAttendance);
|
||||
return updatedAttendance;
|
||||
});
|
||||
resetPage();
|
||||
}
|
||||
},
|
||||
[selectedProject, dateRange, filtering, resetPage]
|
||||
[selectedProject, dateRange, resetPage]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@ -200,18 +156,10 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
||||
(msg) => {
|
||||
const { startDate, endDate } = dateRange;
|
||||
if (data.some((item) => item.employeeId == msg.employeeId)) {
|
||||
// dispatch(
|
||||
// fetchAttendanceData({
|
||||
// ,
|
||||
// fromDate: startDate,
|
||||
// toDate: endDate,
|
||||
// })
|
||||
// );
|
||||
|
||||
refetch()
|
||||
refetch();
|
||||
}
|
||||
},
|
||||
[selectedProject, dateRange, data, refetch]
|
||||
[data, refetch]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@ -219,6 +167,8 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
||||
return () => eventBus.off("employee", employeeHandler);
|
||||
}, [employeeHandler]);
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@ -230,7 +180,7 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
||||
onRangeChange={setDateRange}
|
||||
defaultStartDate={yesterday}
|
||||
/>
|
||||
<div className="form-check form-switch text-start m-0 ms-5">
|
||||
<div className="form-check form-switch text-start ms-1 ms-md-2 align-items-center mb-0">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
@ -243,18 +193,16 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
||||
<label className="form-check-label ms-0">Show Pending</label>
|
||||
</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 className="table-responsive text-nowrap" style={{ minHeight: "200px" }}>
|
||||
<div
|
||||
className="table-responsive text-nowrap"
|
||||
style={{ minHeight: "200px" }}
|
||||
>
|
||||
{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>
|
||||
</div>
|
||||
) : filteredSearchData?.length > 0 ? (
|
||||
@ -265,6 +213,7 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
||||
Name
|
||||
</th>
|
||||
<th className="border-top-1">Date</th>
|
||||
<th>Organization</th>
|
||||
<th>
|
||||
<i className="bx bxs-down-arrow-alt text-success"></i>{" "}
|
||||
Check-In
|
||||
@ -294,7 +243,7 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
||||
key={`header-${currentDate}`}
|
||||
className="table-row-header"
|
||||
>
|
||||
<td colSpan={6} className="text-start">
|
||||
<td colSpan={8} className="text-start">
|
||||
<strong>
|
||||
{moment(currentDate).format("DD-MM-YYYY")}
|
||||
</strong>
|
||||
@ -324,6 +273,7 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
||||
attendance.checkInTime || attendance.checkOutTime
|
||||
).format("DD-MMM-YYYY")}
|
||||
</td>
|
||||
<td>{attendance.organizationName || "--"}</td>
|
||||
<td>{convertShortTime(attendance.checkInTime)}</td>
|
||||
<td>
|
||||
{attendance.checkOutTime
|
||||
@ -345,7 +295,12 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
||||
</tbody>
|
||||
</table>
|
||||
) : (
|
||||
<div className="my-4"><span className="text-secondary">No Record Available !</span></div>
|
||||
<div className="my-12">
|
||||
<span className="text-secondary">
|
||||
No data available for the selected date range. Please Select
|
||||
another date.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{paginatedAttendances?.length == 0 && filteredSearchData?.length > 0 && (
|
||||
@ -371,7 +326,8 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
||||
(pageNumber) => (
|
||||
<li
|
||||
key={pageNumber}
|
||||
className={`page-item ${currentPage === pageNumber ? "active" : ""
|
||||
className={`page-item ${
|
||||
currentPage === pageNumber ? "active" : ""
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
@ -384,7 +340,8 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
||||
)
|
||||
)}
|
||||
<li
|
||||
className={`page-item ${currentPage === totalPages ? "disabled" : ""
|
||||
className={`page-item ${
|
||||
currentPage === totalPages ? "disabled" : ""
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
|
@ -5,7 +5,6 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import TimePicker from "../common/TimePicker";
|
||||
import { usePositionTracker } from "../../hooks/usePositionTracker";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { markAttendance } from "../../slices/apiSlice/attedanceLogsSlice";
|
||||
import showToast from "../../services/toastService";
|
||||
import { checkIfCurrentDate } from "../../utils/dateUtils";
|
||||
import { useMarkAttendance } from "../../hooks/useAttendance";
|
||||
@ -34,7 +33,7 @@ const createSchema = (modeldata) => {
|
||||
const checkOut = new Date(checkIn);
|
||||
checkOut.setHours(hour, minute, 0, 0);
|
||||
|
||||
return checkOut > checkIn;
|
||||
return checkOut >= checkIn;
|
||||
}
|
||||
return true;
|
||||
}, {
|
||||
@ -97,12 +96,12 @@ const CheckInCheckOut = ({ modeldata, closeModal, handleSubmitForm }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="col-12 d-flex justify-content-center">
|
||||
<label className="fs-5 text-dark text-center">
|
||||
<form className="row p-2" onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="col-12 d-flex justify-content-center mb-4">
|
||||
<label className="fs-5 tex-semibold text-center">
|
||||
{modeldata?.checkInTime && !modeldata?.checkOutTime
|
||||
? "Check-out :"
|
||||
: "Check-in :"}
|
||||
? "Check-Out "
|
||||
: "Check-In "}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import "../../components/Project/ProjectInfra.css";
|
||||
import BuildingModel from "../Project/Infrastructure/BuildingModel";
|
||||
@ -8,9 +7,18 @@ import WorkAreaModel from "../Project/Infrastructure/WorkAreaModel";
|
||||
import TaskModel from "../Project/Infrastructure/TaskModel";
|
||||
import ProjectRepository from "../../repositories/ProjectRepository";
|
||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||
import {useProjectDetails, useProjectInfra, useProjects} from "../../hooks/useProjects";
|
||||
import {
|
||||
useCurrentService,
|
||||
useProjectDetails,
|
||||
useProjectInfra,
|
||||
useProjects,
|
||||
} from "../../hooks/useProjects";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import {APPROVE_TASK, ASSIGN_REPORT_TASK, MANAGE_PROJECT_INFRA} from "../../utils/constants";
|
||||
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";
|
||||
@ -18,51 +26,53 @@ import InfraTable from "../Project/Infrastructure/InfraTable";
|
||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
import Loader from "../common/Loader";
|
||||
|
||||
|
||||
const InfraPlanning = () =>
|
||||
{
|
||||
const {profile: LoggedUser, refetch : fetchData} = useProfile()
|
||||
const dispatch = useDispatch()
|
||||
// const selectedProject = useSelector((store)=>store.localVariables.projectId)
|
||||
const InfraPlanning = () => {
|
||||
const { profile: LoggedUser, refetch: fetchData } = useProfile();
|
||||
const dispatch = useDispatch();
|
||||
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 ApprovedTaskRights = useHasUserPermission(APPROVE_TASK)
|
||||
const ReportTaskRights = useHasUserPermission(ASSIGN_REPORT_TASK)
|
||||
const reloadedData = useSelector( ( store ) => store.localVariables.reload )
|
||||
const reloadedData = useSelector((store) => store.localVariables.reload);
|
||||
|
||||
const hasAccess = canManageInfra || canApproveTask || canReportTask;
|
||||
|
||||
// useEffect( () =>
|
||||
// {
|
||||
// if (reloadedData)
|
||||
// {
|
||||
// refetch()
|
||||
// dispatch( refreshData( false ) )
|
||||
// }
|
||||
if (isError) {
|
||||
return <div>{error?.response?.data?.message || error?.message}</div>;
|
||||
}
|
||||
|
||||
// },[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">
|
||||
<p className="my-3">No Result Found</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<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" }}>
|
||||
{(ApprovedTaskRights || ReportTaskRights) ? (
|
||||
<div className="align-items-center">
|
||||
<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>
|
||||
)}
|
||||
<InfraTable buildings={projectInfra} projectId={selectedProject} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,25 +1,43 @@
|
||||
import React, { useCallback, useEffect, useState, useMemo } from "react";
|
||||
import Avatar from "../common/Avatar";
|
||||
import { convertShortTime } from "../../utils/dateUtils";
|
||||
import { convertShortTime, formatUTCToLocalTime } from "../../utils/dateUtils";
|
||||
import RegularizationActions from "./RegularizationActions";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useRegularizationRequests } from "../../hooks/useAttendance";
|
||||
import moment from "moment";
|
||||
import usePagination from "../../hooks/usePagination";
|
||||
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 Pagination from "../../components/common/Pagination";
|
||||
|
||||
const Regularization = ({ handleRequest, searchTerm }) => {
|
||||
const Regularization = ({
|
||||
handleRequest,
|
||||
searchTerm,
|
||||
projectId,
|
||||
organizationId,
|
||||
IncludeInActive,
|
||||
}) => {
|
||||
const queryClient = useQueryClient();
|
||||
// var selectedProject = useSelector((store) => store.localVariables.projectId);
|
||||
const selectedProject = useSelectedProject();
|
||||
const [regularizesList, setregularizedList] = useState([]);
|
||||
const { regularizes, loading, error, refetch } =
|
||||
useRegularizationRequests(selectedProject);
|
||||
const { regularizes, loading, error, refetch } = useRegularizationRequests(
|
||||
selectedProject,
|
||||
organizationId,
|
||||
IncludeInActive
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if(!regularizes) return
|
||||
if(regularizes?.length) {
|
||||
setregularizedList(regularizes);
|
||||
|
||||
}
|
||||
}, [regularizes]);
|
||||
|
||||
const sortByName = (a, b) => {
|
||||
@ -54,18 +72,15 @@ const Regularization = ({ handleRequest, searchTerm }) => {
|
||||
}
|
||||
const lowercasedSearchTerm = searchTerm.toLowerCase();
|
||||
return sortedList.filter((item) => {
|
||||
const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
|
||||
const fullName = `${item?.firstName} ${item?.lastName}`.toLowerCase();
|
||||
return fullName.includes(lowercasedSearchTerm);
|
||||
});
|
||||
}, [regularizesList, searchTerm]);
|
||||
|
||||
const { currentPage, totalPages, currentItems, paginate } =
|
||||
usePagination(filteredSearchData, 20);
|
||||
|
||||
// Reset pagination when the search term or data changes
|
||||
useEffect(() => {
|
||||
|
||||
}, [filteredSearchData]);
|
||||
const { currentPage, totalPages, currentItems, paginate } = usePagination(
|
||||
filteredSearchData,
|
||||
20
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
eventBus.on("regularization", handler);
|
||||
@ -87,9 +102,15 @@ const Regularization = ({ handleRequest, searchTerm }) => {
|
||||
}, [employeeHandler]);
|
||||
|
||||
return (
|
||||
<div className="table-responsive text-nowrap pb-4" style={{ minHeight: "200px" }}>
|
||||
<div
|
||||
className="table-responsive text-nowrap pb-4"
|
||||
style={{ minHeight: "200px" }}
|
||||
>
|
||||
{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>
|
||||
</div>
|
||||
) : currentItems?.length > 0 ? (
|
||||
@ -98,12 +119,19 @@ const Regularization = ({ handleRequest, searchTerm }) => {
|
||||
<tr>
|
||||
<th colSpan={2}>Name</th>
|
||||
<th>Date</th>
|
||||
<th>Organization</th>
|
||||
<th>
|
||||
<i className="bx bxs-down-arrow-alt text-success"></i>Check-In
|
||||
</th>
|
||||
<th>
|
||||
<i className="bx bxs-up-arrow-alt text-danger"></i>Check-Out
|
||||
</th>
|
||||
<th colSpan={2}>
|
||||
Requested By
|
||||
</th>
|
||||
<th >
|
||||
Requested At
|
||||
</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -112,10 +140,7 @@ const Regularization = ({ handleRequest, searchTerm }) => {
|
||||
<tr key={index}>
|
||||
<td colSpan={2}>
|
||||
<div className="d-flex justify-content-start align-items-center">
|
||||
<Avatar
|
||||
firstName={att.firstName}
|
||||
lastName={att.lastName}
|
||||
></Avatar>
|
||||
<Avatar firstName={att.firstName} lastName={att.lastName} />
|
||||
<div className="d-flex flex-column">
|
||||
<a href="#" className="text-heading text-truncate">
|
||||
<span className="fw-normal">
|
||||
@ -126,9 +151,28 @@ const Regularization = ({ handleRequest, searchTerm }) => {
|
||||
</div>
|
||||
</td>
|
||||
<td>{moment(att.checkOutTime).format("DD-MMM-YYYY")}</td>
|
||||
|
||||
<td>{att.organizationName || "--"}</td>
|
||||
|
||||
<td>{convertShortTime(att.checkInTime)}</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 className="text-center ">
|
||||
<RegularizationActions
|
||||
@ -136,7 +180,6 @@ const Regularization = ({ handleRequest, searchTerm }) => {
|
||||
handleRequest={handleRequest}
|
||||
refresh={refetch}
|
||||
/>
|
||||
{/* </div> */}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
@ -154,7 +197,7 @@ const Regularization = ({ handleRequest, searchTerm }) => {
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{!loading && totalPages > 1 && (
|
||||
{/* {!loading && totalPages > 1 && (
|
||||
<nav aria-label="Page ">
|
||||
<ul className="pagination pagination-sm justify-content-end py-1 mt-3">
|
||||
<li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}>
|
||||
@ -192,6 +235,14 @@ const Regularization = ({ handleRequest, searchTerm }) => {
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
)} */}
|
||||
|
||||
{totalPages > 0 && (
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
onPageChange={paginate}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
@ -1,9 +1,7 @@
|
||||
import React, { act, useEffect, useState } from 'react'
|
||||
import useAttendanceStatus, { ACTIONS } from '../../hooks/useAttendanceStatus';
|
||||
// import AttendanceRepository from '../../repositories/AttendanceRepository';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { usePositionTracker } from '../../hooks/usePositionTracker';
|
||||
import {markCurrentAttendance} from '../../slices/apiSlice/attendanceAllSlice';
|
||||
import {cacheData, getCachedData, useSelectedProject} from '../../slices/apiDataManager';
|
||||
import showToast from '../../services/toastService';
|
||||
import { useMarkAttendance } from '../../hooks/useAttendance';
|
||||
|
@ -77,7 +77,7 @@ export const ReportTask = ({ report, closeModal }) => {
|
||||
return (
|
||||
<div className="container m-0">
|
||||
<div className="text-center">
|
||||
<p className="fs-6 fw-semibold">Report Task</p>
|
||||
<p className="fs-5 fw-semibold">Report Task</p>
|
||||
</div>
|
||||
<div className="mb-1 row text-start">
|
||||
<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">
|
||||
Wrok Area :
|
||||
</label>
|
||||
<div className="col-md-8 text-start text-wrap">
|
||||
<label className=" col-form-label">
|
||||
{" "}
|
||||
{report?.workItem?.workArea?.floor?.building?.name}{" "}
|
||||
<i className="bx bx-chevron-right"></i>{" "}
|
||||
{report?.workItem?.workArea?.floor?.floorName}{" "}
|
||||
<i className="bx bx-chevron-right"> </i>
|
||||
<div className="col-md-8 text-start">
|
||||
<div className="text-wrap">
|
||||
{report?.workItem?.workArea?.floor?.building?.name} <i className="bx bx-chevron-right"></i>
|
||||
{report?.workItem?.workArea?.floor?.floorName} <i className="bx bx-chevron-right"></i>
|
||||
{report?.workItem?.workArea?.areaName}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className="mb-1 row text-start">
|
||||
<label htmlFor="html5-email-input" className="col-md-4 col-form-label">
|
||||
|
@ -110,6 +110,8 @@ const ReportTaskComments = ({
|
||||
approvedTask: defaultCompletedTask || 0,
|
||||
});
|
||||
}, [defaultCompletedTask]);
|
||||
|
||||
const completed_Task = watch("approvedTask")
|
||||
return (
|
||||
<div className="p-2 p-sm-1">
|
||||
<div className="modal-body p-sm-4 p-0">
|
||||
@ -339,13 +341,13 @@ const ReportTaskComments = ({
|
||||
<div
|
||||
className={` ${
|
||||
actionAllow && !commentsData.approvedBy
|
||||
? " d-flex justify-content-between"
|
||||
? " d-flex justify-content-between align-items-center"
|
||||
: "text-end"
|
||||
} mt-2`}
|
||||
>
|
||||
<div
|
||||
className={`form-check ${
|
||||
!(actionAllow && !commentsData.approvedBy) && "d-none"
|
||||
!(actionAllow && !commentsData.approvedBy && defaultCompletedTask > completed_Task ) && "d-none"
|
||||
} `}
|
||||
>
|
||||
<input
|
||||
|
@ -4,6 +4,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { string, z } from "zod";
|
||||
import {
|
||||
useActivitiesMaster,
|
||||
useServices,
|
||||
useWorkCategoriesMaster,
|
||||
} from "../../hooks/masterHook/useMaster";
|
||||
import showToast from "../../services/toastService";
|
||||
@ -25,6 +26,8 @@ const SubTask = ({ activity, onClose }) => {
|
||||
const { activities, loading } = useActivitiesMaster();
|
||||
const { categories, categoryLoading } = useWorkCategoriesMaster();
|
||||
const { Task, loading: TaskLoading } = useTaskById(activity?.id);
|
||||
const {data,isError,isLoading,error} = useServices();
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
@ -97,8 +100,8 @@ const SubTask = ({ activity, onClose }) => {
|
||||
};
|
||||
return (
|
||||
<div className="container-xxl my-1">
|
||||
<p className="fw-semibold">Create Sub Task</p>
|
||||
<form className="row g-2" onSubmit={handleSubmit(onSubmitForm)}>
|
||||
<p className="fw-semibold fs-5">Create Sub Task</p>
|
||||
<form className="row g-2 text-start" onSubmit={handleSubmit(onSubmitForm)}>
|
||||
<div className="col-6">
|
||||
<label className="form-label">Building</label>
|
||||
<input
|
||||
@ -128,26 +131,14 @@ const SubTask = ({ activity, onClose }) => {
|
||||
disabled
|
||||
/>
|
||||
</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>
|
||||
)}
|
||||
<label className="form-label">Service</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-sm"
|
||||
value={activity?.workItem?.activityMaster?.activityGroup?.service?.name || ""}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<label className="form-label">Select Activity</label>
|
||||
@ -172,6 +163,27 @@ const SubTask = ({ activity, onClose }) => {
|
||||
)}
|
||||
</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">
|
||||
<label className="form-label">Planned Work</label>
|
||||
<input
|
||||
@ -219,7 +231,15 @@ const SubTask = ({ activity, onClose }) => {
|
||||
)}
|
||||
</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
|
||||
type="submit"
|
||||
className="btn btn-sm btn-primary me-2"
|
||||
@ -227,14 +247,7 @@ const SubTask = ({ activity, onClose }) => {
|
||||
>
|
||||
{isPending ? "Please wait..." : "Submit"}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm btn-secondary"
|
||||
onClick={() => onClose()}
|
||||
disabled={isPending}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</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);
|
107
src/components/DailyProgressRport/TaskReportFilterPanel.jsx
Normal file
@ -0,0 +1,107 @@
|
||||
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 onSubmit = (formData) => {
|
||||
const filterPayload = {
|
||||
...formData,
|
||||
dateFrom: localToUtc(formData.dateFrom),
|
||||
dateTo: localToUtc(formData.dateTo),
|
||||
};
|
||||
handleFilter(filterPayload);
|
||||
};
|
||||
|
||||
const onClear = () => {
|
||||
setResetKey((prev) => prev + 1);
|
||||
handleFilter(TaskReportDefaultValue);
|
||||
reset(TaskReportDefaultValue);
|
||||
};
|
||||
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;
|
301
src/components/DailyProgressRport/TaskReportList.jsx
Normal file
@ -0,0 +1,301 @@
|
||||
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 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>
|
||||
{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 LineChart from "../Charts/LineChart";
|
||||
import { useProjects } from "../../hooks/useProjects";
|
||||
import { useDashboard_ActivityData } from "../../hooks/useDashboard_Data";
|
||||
import ApexChart from "../Charts/Circlechart";
|
||||
// import React, { useState, useEffect } from "react";
|
||||
// import LineChart from "../Charts/LineChart";
|
||||
// import { useProjects } from "../../hooks/useProjects";
|
||||
// import { useDashboard_ActivityData } from "../../hooks/useDashboard_Data";
|
||||
// import ApexChart from "../Charts/Circlechart";
|
||||
|
||||
const LOCAL_STORAGE_PROJECT_KEY = "selectedActivityProjectId";
|
||||
// const LOCAL_STORAGE_PROJECT_KEY = "selectedActivityProjectId";
|
||||
|
||||
const Activity = () => {
|
||||
const { projects } = useProjects();
|
||||
const today = new Date().toISOString().split("T")[0]; // Format: YYYY-MM-DD
|
||||
const [selectedDate, setSelectedDate] = useState(today);
|
||||
const storedProjectId = localStorage.getItem(LOCAL_STORAGE_PROJECT_KEY);
|
||||
const initialProjectId = storedProjectId || "all";
|
||||
const [selectedProjectId, setSelectedProjectId] = useState(initialProjectId);
|
||||
const [displayedProjectName, setDisplayedProjectName] = useState("Select Project");
|
||||
const [activeTab, setActiveTab] = useState("all");
|
||||
// const Activity = () => {
|
||||
// const { projects } = useProjects();
|
||||
// const today = new Date().toISOString().split("T")[0]; // Format: YYYY-MM-DD
|
||||
// const [selectedDate, setSelectedDate] = useState(today);
|
||||
// const storedProjectId = localStorage.getItem(LOCAL_STORAGE_PROJECT_KEY);
|
||||
// const initialProjectId = storedProjectId || "all";
|
||||
// const [selectedProjectId, setSelectedProjectId] = useState(initialProjectId);
|
||||
// const [displayedProjectName, setDisplayedProjectName] = useState("Select Project");
|
||||
// const [activeTab, setActiveTab] = useState("all");
|
||||
|
||||
const { dashboard_Activitydata: ActivityData, isLoading, error: isError } =
|
||||
useDashboard_ActivityData(selectedDate, selectedProjectId);
|
||||
// const { dashboard_Activitydata: ActivityData, isLoading, error: isError } =
|
||||
// useDashboard_ActivityData(selectedDate, selectedProjectId);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedProjectId === "all") {
|
||||
setDisplayedProjectName("All Projects");
|
||||
} else if (projects) {
|
||||
const foundProject = projects.find((p) => p.id === selectedProjectId);
|
||||
setDisplayedProjectName(foundProject ? foundProject.name : "Select Project");
|
||||
} else {
|
||||
setDisplayedProjectName("Select Project");
|
||||
}
|
||||
// useEffect(() => {
|
||||
// if (selectedProjectId === "all") {
|
||||
// setDisplayedProjectName("All Projects");
|
||||
// } else if (projects) {
|
||||
// const foundProject = projects.find((p) => p.id === selectedProjectId);
|
||||
// setDisplayedProjectName(foundProject ? foundProject.name : "Select Project");
|
||||
// } else {
|
||||
// setDisplayedProjectName("Select Project");
|
||||
// }
|
||||
|
||||
localStorage.setItem(LOCAL_STORAGE_PROJECT_KEY, selectedProjectId);
|
||||
}, [selectedProjectId, projects]);
|
||||
// localStorage.setItem(LOCAL_STORAGE_PROJECT_KEY, selectedProjectId);
|
||||
// }, [selectedProjectId, projects]);
|
||||
|
||||
const handleProjectSelect = (projectId) => {
|
||||
setSelectedProjectId(projectId);
|
||||
};
|
||||
// const handleProjectSelect = (projectId) => {
|
||||
// setSelectedProjectId(projectId);
|
||||
// };
|
||||
|
||||
const handleDateChange = (e) => {
|
||||
setSelectedDate(e.target.value);
|
||||
};
|
||||
// const handleDateChange = (e) => {
|
||||
// setSelectedDate(e.target.value);
|
||||
// };
|
||||
|
||||
return (
|
||||
<div className="card h-100">
|
||||
<div className="card-header">
|
||||
<div className="d-flex flex-wrap justify-content-between align-items-center mb-0">
|
||||
<div className="card-title mb-0 text-start">
|
||||
<h5 className="mb-1">Activity</h5>
|
||||
<p className="card-subtitle">Activity Progress Chart</p>
|
||||
</div>
|
||||
// return (
|
||||
// <div className="card h-100">
|
||||
// <div className="card-header">
|
||||
// <div className="d-flex flex-wrap justify-content-between align-items-center mb-0">
|
||||
// <div className="card-title mb-0 text-start">
|
||||
// <h5 className="mb-1">Activity</h5>
|
||||
// <p className="card-subtitle text-primary">Activity Progress Chart</p>
|
||||
// </div>
|
||||
|
||||
<div className="btn-group">
|
||||
<button
|
||||
className="btn btn-outline-primary btn-sm dropdown-toggle"
|
||||
type="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
{displayedProjectName}
|
||||
</button>
|
||||
<ul className="dropdown-menu">
|
||||
<li>
|
||||
<button className="dropdown-item" onClick={() => handleProjectSelect("all")}>
|
||||
All Projects
|
||||
</button>
|
||||
</li>
|
||||
{projects?.map((project) => (
|
||||
<li key={project.id}>
|
||||
<button
|
||||
className="dropdown-item"
|
||||
onClick={() => handleProjectSelect(project.id)}
|
||||
>
|
||||
{project.name}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
// <div className="btn-group">
|
||||
// <button
|
||||
// className="btn btn-outline-primary btn-sm dropdown-toggle"
|
||||
// type="button"
|
||||
// data-bs-toggle="dropdown"
|
||||
// aria-expanded="false"
|
||||
// >
|
||||
// {displayedProjectName}
|
||||
// </button>
|
||||
// <ul className="dropdown-menu">
|
||||
// <li>
|
||||
// <button className="dropdown-item" onClick={() => handleProjectSelect("all")}>
|
||||
// All Projects
|
||||
// </button>
|
||||
// </li>
|
||||
// {projects?.map((project) => (
|
||||
// <li key={project.id}>
|
||||
// <button
|
||||
// className="dropdown-item"
|
||||
// onClick={() => handleProjectSelect(project.id)}
|
||||
// >
|
||||
// {project.name}
|
||||
// </button>
|
||||
// </li>
|
||||
// ))}
|
||||
// </ul>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
{/* ✅ Date Picker Aligned Left with Padding */}
|
||||
<div className="d-flex justify-content-start ps-3 mb-3">
|
||||
<div style={{ width: "150px" }}>
|
||||
<input
|
||||
type="date"
|
||||
className="form-control"
|
||||
value={selectedDate}
|
||||
onChange={handleDateChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
// {/* ✅ Date Picker Aligned Left with Padding */}
|
||||
// <div className="d-flex justify-content-start ps-3 mb-3">
|
||||
// <div style={{ width: "150px" }}>
|
||||
// <input
|
||||
// type="date"
|
||||
// className="form-control"
|
||||
// value={selectedDate}
|
||||
// onChange={handleDateChange}
|
||||
// />
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
{/* Tabs */}
|
||||
<ul className="nav nav-tabs " role="tablist">
|
||||
<li className="nav-item">
|
||||
<button
|
||||
type="button"
|
||||
className={`nav-link ${activeTab === "all" ? "active" : ""}`}
|
||||
onClick={() => setActiveTab("all")}
|
||||
data-bs-toggle="tab"
|
||||
>
|
||||
Summary
|
||||
</button>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<button
|
||||
type="button"
|
||||
className={`nav-link ${activeTab === "logs" ? "active" : ""}`}
|
||||
onClick={() => setActiveTab("logs")}
|
||||
data-bs-toggle="tab"
|
||||
>
|
||||
Details
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
// {/* Tabs */}
|
||||
// <ul className="nav nav-tabs " role="tablist">
|
||||
// <li className="nav-item">
|
||||
// <button
|
||||
// type="button"
|
||||
// className={`nav-link ${activeTab === "all" ? "active" : ""}`}
|
||||
// onClick={() => setActiveTab("all")}
|
||||
// data-bs-toggle="tab"
|
||||
// >
|
||||
// Summary
|
||||
// </button>
|
||||
// </li>
|
||||
// <li className="nav-item">
|
||||
// <button
|
||||
// type="button"
|
||||
// className={`nav-link ${activeTab === "logs" ? "active" : ""}`}
|
||||
// onClick={() => setActiveTab("logs")}
|
||||
// data-bs-toggle="tab"
|
||||
// >
|
||||
// Details
|
||||
// </button>
|
||||
// </li>
|
||||
// </ul>
|
||||
|
||||
<div className="card-body">
|
||||
{activeTab === "all" && (
|
||||
<div className="row justify-content-between">
|
||||
<div className="col-md-6 d-flex flex-column align-items-center text-center mb-4">
|
||||
{isLoading ? (
|
||||
<p>Loading activity data...</p>
|
||||
) : isError ? (
|
||||
<p>No data available.</p>
|
||||
) : (
|
||||
ActivityData && (
|
||||
<>
|
||||
<h5 className="fw-bold mb-0 text-start w-80">
|
||||
<i className="bx bx-task text-info"></i> Allocated Task
|
||||
</h5>
|
||||
<h4 className="mb-0 fw-bold">
|
||||
{ActivityData.totalCompletedWork?.toLocaleString()}/
|
||||
{ActivityData.totalPlannedWork?.toLocaleString()}
|
||||
</h4>
|
||||
<small className="text-muted">Completed / Assigned</small>
|
||||
<div style={{ maxWidth: "180px" }}>
|
||||
<ApexChart />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
// <div className="card-body">
|
||||
// {activeTab === "all" && (
|
||||
// <div className="row justify-content-between">
|
||||
// <div className="col-md-6 d-flex flex-column align-items-center text-center mb-4">
|
||||
// {isLoading ? (
|
||||
// <p>Loading activity data...</p>
|
||||
// ) : isError ? (
|
||||
// <p>No data available.</p>
|
||||
// ) : (
|
||||
// ActivityData && (
|
||||
// <>
|
||||
// <h5 className="fw-bold mb-0 text-start w-80">
|
||||
// <i className="bx bx-task text-info"></i> Allocated Task
|
||||
// </h5>
|
||||
// <h4 className="mb-0 fw-bold">
|
||||
// {ActivityData.totalCompletedWork?.toLocaleString()}/
|
||||
// {ActivityData.totalPlannedWork?.toLocaleString()}
|
||||
// </h4>
|
||||
// <small className="text-muted">Completed / Assigned</small>
|
||||
// <div style={{ maxWidth: "180px" }}>
|
||||
// <ApexChart />
|
||||
// </div>
|
||||
// </>
|
||||
// )
|
||||
// )}
|
||||
// </div>
|
||||
|
||||
<div className="col-md-6 d-flex flex-column align-items-center text-center mb-4">
|
||||
{!isLoading && !isError && ActivityData && (
|
||||
<>
|
||||
<h5 className="fw-bold mb-0 text-start w-110">
|
||||
<i className="bx bx-task text-info"></i> Activities
|
||||
</h5>
|
||||
<h4 className="mb-0 fw-bold">
|
||||
{ActivityData.totalCompletedWork?.toLocaleString()}/
|
||||
{ActivityData.totalPlannedWork?.toLocaleString()}
|
||||
</h4>
|
||||
<small className="text-muted ">Pending / Assigned</small>
|
||||
<div style={{ maxWidth: "180px" }}>
|
||||
<ApexChart />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
// <div className="col-md-6 d-flex flex-column align-items-center text-center mb-4">
|
||||
// {!isLoading && !isError && ActivityData && (
|
||||
// <>
|
||||
// <h5 className="fw-bold mb-0 text-start w-110">
|
||||
// <i className="bx bx-task text-info"></i> Activities
|
||||
// </h5>
|
||||
// <h4 className="mb-0 fw-bold">
|
||||
// {ActivityData.totalCompletedWork?.toLocaleString()}/
|
||||
// {ActivityData.totalPlannedWork?.toLocaleString()}
|
||||
// </h4>
|
||||
// <small className="text-muted ">Pending / Assigned</small>
|
||||
// <div style={{ maxWidth: "180px" }}>
|
||||
// <ApexChart />
|
||||
// </div>
|
||||
// </>
|
||||
// )}
|
||||
// </div>
|
||||
// </div>
|
||||
// )}
|
||||
|
||||
{activeTab === "logs" && (
|
||||
<div className="table-responsive">
|
||||
<table className="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Activity / Location</th>
|
||||
<th>Assigned / Completed</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{[{
|
||||
activity: "Code Review / Remote",
|
||||
assignedToday: 3,
|
||||
completed: 2
|
||||
}].map((log, index) => (
|
||||
<tr key={index}>
|
||||
<td>{log.activity}</td>
|
||||
<td>{log.assignedToday} / {log.completed}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
// {activeTab === "logs" && (
|
||||
// <div className="table-responsive">
|
||||
// <table className="table table-bordered table-hover">
|
||||
// <thead>
|
||||
// <tr>
|
||||
// <th>Activity / Location</th>
|
||||
// <th>Assigned / Completed</th>
|
||||
// </tr>
|
||||
// </thead>
|
||||
// <tbody>
|
||||
// {[{
|
||||
// activity: "Code Review / Remote",
|
||||
// assignedToday: 3,
|
||||
// completed: 2
|
||||
// }].map((log, index) => (
|
||||
// <tr key={index}>
|
||||
// <td>{log.activity}</td>
|
||||
// <td>{log.assignedToday} / {log.completed}</td>
|
||||
// </tr>
|
||||
// ))}
|
||||
// </tbody>
|
||||
// </table>
|
||||
// </div>
|
||||
// )}
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// };
|
||||
|
||||
export default Activity;
|
||||
// export default Activity;
|
||||
|
@ -1,21 +1,16 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import LineChart from "../Charts/LineChart";
|
||||
import React, { useState, useMemo } from "react";
|
||||
import ApexChart from "../Charts/Circle";
|
||||
import { useProjects } from "../../hooks/useProjects";
|
||||
import { useDashboard_AttendanceData } from "../../hooks/useDashboard_Data";
|
||||
import ApexChart from "../Charts/Circle";
|
||||
|
||||
const LOCAL_STORAGE_PROJECT_KEY = "selectedAttendanceProjectId";
|
||||
import { useSelectedProject } from "../../hooks/useSelectedProject"; // ✅ your custom hook
|
||||
|
||||
const Attendance = () => {
|
||||
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 storedProjectId = localStorage.getItem(LOCAL_STORAGE_PROJECT_KEY);
|
||||
const initialProjectId = storedProjectId || "all";
|
||||
const [selectedProjectId, setSelectedProjectId] = useState(initialProjectId);
|
||||
const [displayedProjectName, setDisplayedProjectName] =
|
||||
useState("Select Project");
|
||||
const [activeTab, setActiveTab] = useState("Summary");
|
||||
|
||||
// central project selection hook
|
||||
const selectedProjectId = useSelectedProject()
|
||||
|
||||
const {
|
||||
dashboard_Attendancedata: AttendanceData,
|
||||
@ -23,38 +18,24 @@ const Attendance = () => {
|
||||
error: isError,
|
||||
} = useDashboard_AttendanceData(selectedDate, selectedProjectId);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedProjectId === "all") {
|
||||
setDisplayedProjectName("All Projects");
|
||||
} else if (projects) {
|
||||
const foundProject = projects.find((p) => p.id === selectedProjectId);
|
||||
setDisplayedProjectName(
|
||||
foundProject ? foundProject.name : "Select Project"
|
||||
);
|
||||
} else {
|
||||
setDisplayedProjectName("Select Project");
|
||||
}
|
||||
|
||||
localStorage.setItem(LOCAL_STORAGE_PROJECT_KEY, selectedProjectId);
|
||||
// project name derived once
|
||||
const displayedProjectName = useMemo(() => {
|
||||
if (selectedProjectId === "all") return "All Projects";
|
||||
const found = projects?.find((p) => p.id === selectedProjectId);
|
||||
return found?.name || "Select Project";
|
||||
}, [selectedProjectId, projects]);
|
||||
|
||||
const handleProjectSelect = (projectId) => {
|
||||
setSelectedProjectId(projectId);
|
||||
};
|
||||
|
||||
const handleDateChange = (e) => {
|
||||
setSelectedDate(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="card h-100">
|
||||
{/* Header */}
|
||||
<div className="card-header mb-1 pb-0">
|
||||
<div className="d-flex flex-wrap justify-content-between align-items-center mb-0 pb-0 ">
|
||||
<div className="d-flex flex-wrap justify-content-between align-items-center">
|
||||
<div className="card-title mb-0 text-start">
|
||||
<h5 className="mb-1">Attendance</h5>
|
||||
<p className="card-subtitle">Daily Attendance Data</p>
|
||||
</div>
|
||||
|
||||
{/* Project Dropdown */}
|
||||
<div className="btn-group">
|
||||
<button
|
||||
className="btn btn-outline-primary btn-sm dropdown-toggle"
|
||||
@ -68,7 +49,7 @@ const Attendance = () => {
|
||||
<li>
|
||||
<button
|
||||
className="dropdown-item"
|
||||
onClick={() => handleProjectSelect("all")}
|
||||
onClick={() => setSelectedProjectId("all")}
|
||||
>
|
||||
All Projects
|
||||
</button>
|
||||
@ -77,7 +58,7 @@ const Attendance = () => {
|
||||
<li key={project.id}>
|
||||
<button
|
||||
className="dropdown-item"
|
||||
onClick={() => handleProjectSelect(project.id)}
|
||||
onClick={() => setSelectedProjectId(project.id)}
|
||||
>
|
||||
{project.name}
|
||||
</button>
|
||||
@ -88,18 +69,14 @@ const Attendance = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="d-flex flex-wrap justify-content-between align-items-center mb-0 mt-0 me-5 ms-5">
|
||||
{/* Tabs */}
|
||||
<div>
|
||||
<ul className="nav nav-tabs " role="tablist">
|
||||
{/* Tabs + Date Picker */}
|
||||
<div className="d-flex flex-wrap justify-content-between align-items-center me-5 ms-5">
|
||||
<ul className="nav nav-tabs">
|
||||
<li className="nav-item">
|
||||
<button
|
||||
type="button"
|
||||
className={`nav-link ${
|
||||
activeTab === "Summary" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => setActiveTab("Summary")}
|
||||
data-bs-toggle="tab"
|
||||
className={`nav-link ${AttendanceData?.activeTab === "Summary" ? "active" : ""}`}
|
||||
onClick={() => (AttendanceData.activeTab = "Summary")}
|
||||
>
|
||||
Summary
|
||||
</button>
|
||||
@ -107,33 +84,28 @@ const Attendance = () => {
|
||||
<li className="nav-item">
|
||||
<button
|
||||
type="button"
|
||||
className={`nav-link ${
|
||||
activeTab === "Details" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => setActiveTab("Details")}
|
||||
data-bs-toggle="tab"
|
||||
className={`nav-link ${AttendanceData?.activeTab === "Details" ? "active" : ""}`}
|
||||
onClick={() => (AttendanceData.activeTab = "Details")}
|
||||
>
|
||||
Details
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{/* ✅ Date Picker Aligned Left with Padding */}
|
||||
<div className="ps-6 mb-3 mt-0">
|
||||
<div style={{ width: "120px" }}>
|
||||
<div className="ps-6 mb-3">
|
||||
<input
|
||||
type="date"
|
||||
className="form-control p-1"
|
||||
// style={{ fontSize: "1rem" }}
|
||||
style={{ width: "120px" }}
|
||||
value={selectedDate}
|
||||
onChange={handleDateChange}
|
||||
onChange={(e) => setSelectedDate(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className="card-body">
|
||||
{activeTab === "Summary" && (
|
||||
{/* Summary */}
|
||||
{AttendanceData?.activeTab === "Summary" && (
|
||||
<div className="row justify-content-center">
|
||||
<div className="col-12 col-md-6 d-flex flex-column align-items-center text-center mb-4">
|
||||
{isLoading ? (
|
||||
@ -143,7 +115,7 @@ const Attendance = () => {
|
||||
) : (
|
||||
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
|
||||
</h5>
|
||||
<h4 className="mb-0 fw-bold">
|
||||
@ -164,11 +136,9 @@ const Attendance = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === "Details" && (
|
||||
<div
|
||||
className="table-responsive"
|
||||
style={{ maxHeight: "300px", overflowY: "auto" }}
|
||||
>
|
||||
{/* Details */}
|
||||
{AttendanceData?.activeTab === "Details" && (
|
||||
<div className="table-responsive" style={{ maxHeight: "300px" }}>
|
||||
<table className="table table-hover mb-0 text-start">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -178,32 +148,17 @@ const Attendance = () => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{AttendanceData?.attendanceTable &&
|
||||
AttendanceData.attendanceTable.length > 0 ? (
|
||||
AttendanceData.attendanceTable.map((record, index) => (
|
||||
<tr key={index}>
|
||||
<td>
|
||||
{record.firstName} {record.lastName}
|
||||
</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>
|
||||
{AttendanceData?.attendanceTable?.length ? (
|
||||
AttendanceData.attendanceTable.map((r, i) => (
|
||||
<tr key={i}>
|
||||
<td>{r.firstName} {r.lastName}</td>
|
||||
<td>{r.inTime ? new Date(r.inTime).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "-"}</td>
|
||||
<td>{r.outTime ? new Date(r.outTime).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "-"}</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan="3" className="text-center">
|
||||
No attendance data available
|
||||
</td>
|
||||
<td colSpan="3" className="text-center">No attendance data available</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
|
@ -99,9 +99,7 @@ const AttendanceOverview = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="bg-white p-4 rounded shadow d-flex flex-column"
|
||||
>
|
||||
<div className="bg-white p-4 rounded shadow d-flex flex-column">
|
||||
{/* Header */}
|
||||
<div className="d-flex justify-content-between align-items-center mb-3">
|
||||
<div className="card-title mb-0 text-start">
|
||||
@ -119,18 +117,22 @@ const AttendanceOverview = () => {
|
||||
<option value={30}>Last 30 Days</option>
|
||||
</select>
|
||||
<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")}
|
||||
title="Chart View"
|
||||
>
|
||||
<i className="bx bx-bar-chart-alt-2"></i>
|
||||
</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")}
|
||||
title="Table View"
|
||||
>
|
||||
<i className="bx bx-task text-success"></i>
|
||||
<i className="bx bx-list-ul fs-5"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,33 +1,28 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import { useDashboardProjectsCardData } from "../../hooks/useDashboard_Data";
|
||||
import eventBus from "../../services/eventBus";
|
||||
import GlobalRepository from "../../repositories/GlobalRepository";
|
||||
|
||||
const Projects = () => {
|
||||
const { projectsCardData } = useDashboardProjectsCardData();
|
||||
const [projectData, setProjectsData] = useState(projectsCardData);
|
||||
const {
|
||||
data: projectsCardData,
|
||||
isLoading,
|
||||
isError,
|
||||
error,
|
||||
refetch,
|
||||
} = useDashboardProjectsCardData();
|
||||
|
||||
useEffect(() => {
|
||||
setProjectsData(projectsCardData);
|
||||
}, [projectsCardData]);
|
||||
// When "project" event happens, just refetch
|
||||
const handler = () => {
|
||||
refetch();
|
||||
};
|
||||
|
||||
const handler = useCallback(
|
||||
async (msg) => {
|
||||
try {
|
||||
const response =
|
||||
await GlobalRepository.getDashboardProjectsCardData();
|
||||
setProjectsData(response.data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
},
|
||||
[GlobalRepository]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
eventBus.on("project", handler);
|
||||
return () => eventBus.off("project", handler);
|
||||
}, [handler]);
|
||||
}, [refetch]);
|
||||
|
||||
const totalProjects = projectsCardData?.totalProjects ?? 0;
|
||||
const ongoingProjects = projectsCardData?.ongoingProjects ?? 0;
|
||||
|
||||
return (
|
||||
<div className="card p-3 h-100 text-center d-flex justify-content-between">
|
||||
@ -37,20 +32,29 @@ const Projects = () => {
|
||||
Projects
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
<div className="d-flex justify-content-center align-items-center flex-grow-1">
|
||||
<div className="spinner-border text-primary" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
) : isError ? (
|
||||
<div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">
|
||||
{error?.message || "Error loading data"}
|
||||
</div>
|
||||
) : (
|
||||
<div className="d-flex justify-content-around align-items-start mt-n2">
|
||||
<div>
|
||||
<h4 className="mb-0 fw-bold">
|
||||
{projectData.totalProjects?.toLocaleString()}
|
||||
</h4>
|
||||
<h4 className="mb-0 fw-bold">{totalProjects.toLocaleString()}</h4>
|
||||
<small className="text-muted">Total</small>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="mb-0 fw-bold">
|
||||
{projectData.ongoingProjects?.toLocaleString()}
|
||||
</h4>
|
||||
<h4 className="mb-0 fw-bold">{ongoingProjects.toLocaleString()}</h4>
|
||||
<small className="text-muted">Ongoing</small>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,10 +1,16 @@
|
||||
import React from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
import { useDashboardTasksCardData } from "../../hooks/useDashboard_Data";
|
||||
|
||||
const TasksCard = () => {
|
||||
const projectId = useSelector((store) => store.localVariables?.projectId);
|
||||
const { tasksCardData, loading, error } = useDashboardTasksCardData(projectId);
|
||||
const projectId = useSelectedProject();
|
||||
|
||||
const {
|
||||
data: tasksCardData,
|
||||
isLoading,
|
||||
isError,
|
||||
error,
|
||||
} = useDashboardTasksCardData(projectId);
|
||||
|
||||
return (
|
||||
<div className="card p-3 h-100 text-center d-flex justify-content-between">
|
||||
@ -14,28 +20,30 @@ const TasksCard = () => {
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
// Loader will be displayed when loading is true
|
||||
{isLoading ? (
|
||||
// Loader while fetching
|
||||
<div className="d-flex justify-content-center align-items-center flex-grow-1">
|
||||
<div className="spinner-border text-primary" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
) : error ? (
|
||||
// Error message if there's an error
|
||||
<div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">{error}</div>
|
||||
) : isError ? (
|
||||
// Show error
|
||||
<div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">
|
||||
{error?.message || "Error loading data"}
|
||||
</div>
|
||||
) : (
|
||||
// Actual data when loaded successfully
|
||||
// Show data
|
||||
<div className="d-flex justify-content-around align-items-start mt-n2">
|
||||
<div>
|
||||
<h4 className="mb-0 fw-bold">
|
||||
{tasksCardData?.totalTasks?.toLocaleString()}
|
||||
{tasksCardData?.totalTasks?.toLocaleString() ?? 0}
|
||||
</h4>
|
||||
<small className="text-muted">Total</small>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="mb-0 fw-bold">
|
||||
{tasksCardData?.completedTasks?.toLocaleString()}
|
||||
{tasksCardData?.completedTasks?.toLocaleString() ?? 0}
|
||||
</h4>
|
||||
<small className="text-muted">Completed</small>
|
||||
</div>
|
||||
|
@ -1,33 +1,45 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useDashboardTeamsCardData } from "../../hooks/useDashboard_Data";
|
||||
import eventBus from "../../services/eventBus";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
|
||||
const Teams = () => {
|
||||
const projectId = useSelector((store) => store.localVariables?.projectId);
|
||||
const { teamsCardData, loading, error } = useDashboardTeamsCardData(projectId);
|
||||
const queryClient = useQueryClient();
|
||||
const projectId = useSelectedProject()
|
||||
|
||||
const [totalEmployees, setTotalEmployee] = useState(0);
|
||||
const [inToday, setInToday] = useState(0);
|
||||
|
||||
// Update state when API data arrives
|
||||
useEffect(() => {
|
||||
setTotalEmployee(teamsCardData?.totalEmployees || 0);
|
||||
setInToday(teamsCardData?.inToday || 0);
|
||||
}, [teamsCardData]);
|
||||
const {
|
||||
data: teamsCardData,
|
||||
isLoading,
|
||||
isError,
|
||||
error,
|
||||
} = useDashboardTeamsCardData(projectId);
|
||||
|
||||
// Handle real-time updates via eventBus
|
||||
const handler = useCallback((msg) => {
|
||||
const handler = useCallback(
|
||||
(msg) => {
|
||||
if (msg.activity === 1) {
|
||||
setInToday((prev) => prev + 1);
|
||||
queryClient.setQueryData(["dashboardTeams", projectId], (old) => {
|
||||
if (!old) return old;
|
||||
return {
|
||||
...old,
|
||||
inToday: (old.inToday || 0) + 1,
|
||||
};
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
},
|
||||
[queryClient, projectId]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
eventBus.on("attendance", handler);
|
||||
return () => eventBus.off("attendance", handler);
|
||||
}, [handler]);
|
||||
|
||||
const inToday = teamsCardData?.inToday ?? 0;
|
||||
const totalEmployees = teamsCardData?.totalEmployees ?? 0;
|
||||
|
||||
return (
|
||||
<div className="card p-3 h-100 text-center d-flex justify-content-between">
|
||||
<div className="d-flex justify-content-start align-items-center mb-3">
|
||||
@ -36,18 +48,17 @@ const Teams = () => {
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
// Blue spinner loader
|
||||
{isLoading ? (
|
||||
<div className="d-flex justify-content-center align-items-center flex-grow-1">
|
||||
<div className="spinner-border text-primary" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
) : error ? (
|
||||
// Error message if data fetching fails
|
||||
<div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">{error}</div>
|
||||
) : isError ? (
|
||||
<div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">
|
||||
{error?.message || "Error loading data"}
|
||||
</div>
|
||||
) : (
|
||||
// Display data once loaded
|
||||
<div className="d-flex justify-content-around align-items-start mt-n2">
|
||||
<div>
|
||||
<h4 className="mb-0 fw-bold">{totalEmployees.toLocaleString()}</h4>
|
||||
|
68
src/components/Directory/AssignedBucket.jsx
Normal file
@ -0,0 +1,68 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import EmployeeList from "./EmployeeList";
|
||||
import { useAllEmployees } from "../../hooks/useEmployees";
|
||||
import showToast from "../../services/toastService";
|
||||
import { DirectoryRepository } from "../../repositories/DirectoryRepository";
|
||||
import { useAssignEmpToBucket } from "../../hooks/useDirectory";
|
||||
|
||||
const AssignedBucket = ({ selectedBucket, handleClose }) => {
|
||||
const { employeesList } = useAllEmployees(false);
|
||||
const [selectedEmployees, setSelectedEmployees] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedBucket) {
|
||||
const preselected = employeesList
|
||||
.filter((emp) => selectedBucket?.employeeIds?.includes(emp.employeeId))
|
||||
.map((emp) => ({ ...emp, isActive: true }));
|
||||
|
||||
setSelectedEmployees(preselected);
|
||||
}
|
||||
}, [selectedBucket, employeesList]);
|
||||
|
||||
const { mutate: AssignEmployee, isPending } = useAssignEmpToBucket(() =>
|
||||
handleClose()
|
||||
);
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const existingEmployeeIds = selectedBucket?.employeeIds || [];
|
||||
|
||||
const employeesToUpdate = selectedEmployees.filter((emp) => {
|
||||
const isExisting = existingEmployeeIds.includes(emp.employeeId);
|
||||
return (!isExisting && emp.isActive) || (isExisting && !emp.isActive);
|
||||
});
|
||||
|
||||
if (employeesToUpdate.length === 0) {
|
||||
showToast("No changes to update", "info");
|
||||
return;
|
||||
}
|
||||
|
||||
AssignEmployee({
|
||||
bucketId: selectedBucket.id,
|
||||
EmployeePayload: employeesToUpdate.map((emp) => ({
|
||||
employeeId: emp.employeeId,
|
||||
isActive: emp.isActive,
|
||||
})),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<EmployeeList
|
||||
employees={employeesList}
|
||||
bucket={selectedBucket}
|
||||
selectedEmployees={selectedEmployees}
|
||||
onChange={setSelectedEmployees}
|
||||
/>
|
||||
|
||||
<div className="mt-3 d-flex justify-content-end gap-3">
|
||||
<button type="submit" className="btn btn-sm btn-primary" disabled={isPending}>
|
||||
{isPending ? "Please Wait...":"Assign"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default AssignedBucket;
|
95
src/components/Directory/BucketForm.jsx
Normal file
@ -0,0 +1,95 @@
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { bucketScheam } from "./DirectorySchema";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import Label from "../common/Label";
|
||||
|
||||
const BucketForm = ({ selectedBucket, mode, onSubmit, onCancel, isPending }) => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver: zodResolver(bucketScheam),
|
||||
defaultValues: selectedBucket || { name: "", description: "" },
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
reset(selectedBucket || { name: "", description: "" });
|
||||
}, [selectedBucket, reset]);
|
||||
|
||||
const isEditMode = mode === "edit";
|
||||
const isCreateMode = mode === "create";
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="d-flex justify-content-between align-items-center">
|
||||
<i
|
||||
className="bx bx-left-arrow-alt bx-md cursor-pointer"
|
||||
onClick={onCancel}
|
||||
></i>
|
||||
|
||||
{/* Show edit toggle only for existing bucket in edit mode */}
|
||||
{/* {isEditMode && (
|
||||
<i className="bx bx-edit bx-sm text-primary cursor-pointer"></i>
|
||||
)} */}
|
||||
</div>
|
||||
|
||||
{(isCreateMode || isEditMode) ? (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="mb-3 mt-5">
|
||||
<Label htmlFor="Name" className="text-start" required>
|
||||
Name
|
||||
</Label>
|
||||
<input
|
||||
className="form-control form-control-sm"
|
||||
{...register("name")}
|
||||
/>
|
||||
{errors.name && (
|
||||
<small className="danger-text">{errors.name.message}</small>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<Label htmlFor="description" className="text-start" required>
|
||||
Description
|
||||
</Label>
|
||||
<textarea
|
||||
className="form-control form-control-sm"
|
||||
{...register("description")}
|
||||
rows="3"
|
||||
/>
|
||||
{errors.description && (
|
||||
<small className="danger-text">{errors.description.message}</small>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-4 mb-3 d-flex gap-3 justify-content-end">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm btn-label-secondary"
|
||||
onClick={onCancel}
|
||||
disabled={isPending}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" className="btn btn-sm btn-primary" disabled={isPending}>
|
||||
{isPending ? "Please Wait " : isEditMode ? "Update" : "Create"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
<dl className="row text-start my-2">
|
||||
<dt className="col-sm-2">Name</dt>
|
||||
<dd className="col-sm-10">{selectedBucket?.name || "-"}</dd>
|
||||
|
||||
<dt className="col-sm-2">Description</dt>
|
||||
<dd className="col-sm-10">{selectedBucket?.description || "-"}</dd>
|
||||
</dl>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BucketForm;
|
53
src/components/Directory/BucketList.jsx
Normal file
@ -0,0 +1,53 @@
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import { useProfile } from "../../hooks/useProfile";
|
||||
import { DIRECTORY_ADMIN, DIRECTORY_MANAGER } from "../../utils/constants";
|
||||
|
||||
const BucketList = ({ buckets, loading, searchTerm, onEdit, onDelete }) => {
|
||||
const { profile } = useProfile();
|
||||
const IsDirecrory_Admin = useHasUserPermission(DIRECTORY_ADMIN);
|
||||
const IsDirectory_Manager = useHasUserPermission(DIRECTORY_MANAGER);
|
||||
const sorted = buckets.filter((bucket) =>
|
||||
bucket.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
|
||||
if (loading) return <div>Loading...</div>;
|
||||
if (!loading && sorted.length === 0) return <div>No buckets found</div>;
|
||||
|
||||
return (
|
||||
<div className="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-3 pt-3 px-2 px-sm-0">
|
||||
{sorted.map((bucket) => (
|
||||
<div className="col" key={bucket.id}>
|
||||
<div className="card h-100">
|
||||
<div className="card-body p-4">
|
||||
<h6 className="card-title d-flex justify-content-between">
|
||||
<span>{bucket.name}</span>
|
||||
{(IsDirecrory_Admin ||
|
||||
IsDirectory_Manager ||
|
||||
bucket?.createdBy?.id === profile?.employeeInfo?.id) && (
|
||||
<div className="d-flex gap-2">
|
||||
<i
|
||||
className="bx bx-edit bx-sm text-primary cursor-pointer"
|
||||
onClick={() => onEdit(bucket)}
|
||||
/>
|
||||
<i
|
||||
className="bx bx-trash bx-sm text-danger cursor-pointer"
|
||||
onClick={() => onDelete(bucket?.id)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</h6>
|
||||
<h6 className="card-subtitle mb-2 text-muted">
|
||||
Contacts: {bucket.numberOfContacts || 0}
|
||||
</h6>
|
||||
<p className="card-text">
|
||||
{bucket.description || "No description"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BucketList;
|
217
src/components/Directory/CardViewContact.jsx
Normal file
@ -0,0 +1,217 @@
|
||||
import React, { useState } from "react";
|
||||
import Avatar from "../common/Avatar";
|
||||
import { getBucketNameById } from "./DirectoryUtils";
|
||||
import { useActiveInActiveContact, useBuckets } from "../../hooks/useDirectory";
|
||||
import { getPhoneIcon } from "./DirectoryUtils";
|
||||
import { useDir } from "../../Context/DireContext";
|
||||
import { useDirectoryContext } from "../../pages/Directory/DirectoryPage";
|
||||
import ConfirmModal from "../common/ConfirmModal";
|
||||
const CardViewContact = ({
|
||||
IsActive,
|
||||
contact,
|
||||
setSelectedContact,
|
||||
setIsOpenModal,
|
||||
setOpen_contact,
|
||||
setIsOpenModalNote,
|
||||
IsDeleted,
|
||||
restore,
|
||||
}) => {
|
||||
const { data, setManageContact, setContactOpen } = useDirectoryContext();
|
||||
const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
|
||||
const { mutate: ActiveInActive, isPending } = useActiveInActiveContact();
|
||||
const handleActiveInactive = (contactId) => {
|
||||
ActiveInActive({ contactId, contactStatus: !IsActive });
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<ConfirmModal
|
||||
type="delete"
|
||||
header="Delete Contact"
|
||||
message="Are you sure you want delete?"
|
||||
onSubmit={handleActiveInactive}
|
||||
onClose={() => setIsDeleteModalOpen(false)}
|
||||
loading={isPending}
|
||||
paramData={contact.id}
|
||||
isOpen={IsDeleteModalOpen}
|
||||
/>
|
||||
|
||||
<div
|
||||
className="card text-start border-1"
|
||||
style={{ background: `${!IsActive ? "#f8f6f6" : ""}` }}
|
||||
>
|
||||
<div className="card-body px-1 py-2 pb-0">
|
||||
<div className="d-flex justify-content-between">
|
||||
<div
|
||||
className={`d-flex align-items-center ${
|
||||
IsActive && "cursor-pointer"
|
||||
}`}
|
||||
onClick={() => {
|
||||
if (IsActive) {
|
||||
setContactOpen({ contact: contact, Open: true });
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
size="xs"
|
||||
firstName={
|
||||
(contact?.name || "").trim().split(" ")[0]?.charAt(0) || ""
|
||||
}
|
||||
lastName={
|
||||
(contact?.name || "").trim().split(" ")[1]?.charAt(0) || ""
|
||||
}
|
||||
/>{" "}
|
||||
<span className="text-heading fs-6"> {contact?.name}</span>
|
||||
</div>
|
||||
<div>
|
||||
{IsActive && (
|
||||
<div className="dropdown z-2">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-xs btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0 m-0"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<i
|
||||
className="bx bx-dots-vertical-rounded text-muted p-0"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-offset="0,8"
|
||||
data-bs-placement="top"
|
||||
data-bs-custom-class="tooltip-dark"
|
||||
title="More Action"
|
||||
></i>
|
||||
</button>
|
||||
<ul className="dropdown-menu dropdown-menu-end w-auto">
|
||||
<li
|
||||
onClick={() =>
|
||||
setManageContact({
|
||||
isOpen: true,
|
||||
contactId: contact?.id,
|
||||
})
|
||||
}
|
||||
>
|
||||
<a className="dropdown-item px-2 cursor-pointer py-1">
|
||||
<i className="bx bx-edit bx-xs text-primary me-2"></i>
|
||||
<span className="align-left ">Modify</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className="dropdown-item px-2 cursor-pointer py-1"
|
||||
onClick={() => setIsDeleteModalOpen(true)}
|
||||
>
|
||||
<i className="bx bx-trash text-danger bx-xs me-2"></i>
|
||||
<span className="align-left">Delete</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{!IsActive && (
|
||||
<i
|
||||
className={`bx ${
|
||||
isPending ? "bx-loader-alt bx-spin" : "bx-recycle"
|
||||
} me-1 text-primary cursor-pointer`}
|
||||
title="Restore"
|
||||
onClick={() => handleActiveInactive(contact.id)}
|
||||
></i>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul className="list-inline m-0 ps-4 d-flex align-items-start">
|
||||
<li className="list-inline-item text-break small px-1 ms-5">
|
||||
{contact?.organization}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div
|
||||
className={`card-footer text-start px-9 py-1 ${
|
||||
IsActive && "cursor-pointer"
|
||||
}`}
|
||||
onClick={() => {
|
||||
if (IsActive) {
|
||||
setIsOpenModalNote(true);
|
||||
setOpen_contact(contact);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<hr className="my-0" />
|
||||
{contact?.designation && (
|
||||
<ul className="list-unstyled my-1 d-flex align-items-start ms-2">
|
||||
<li className="me-2">
|
||||
<i className="fa-solid fa-id-badge ms-1"></i>
|
||||
</li>
|
||||
<li className="flex-grow-1 text-break small">
|
||||
{contact.designation}
|
||||
</li>
|
||||
</ul>
|
||||
)}
|
||||
{contact.contactEmails[0] && (
|
||||
<ul className="list-unstyled my-1 d-flex align-items-start ms-2">
|
||||
<li className="me-2">
|
||||
<i className="bx bx-envelope bx-xs mt-1"></i>
|
||||
</li>
|
||||
<li className="flex-grow-1 text-break small">
|
||||
{contact.contactEmails[0].emailAddress}
|
||||
</li>
|
||||
</ul>
|
||||
)}
|
||||
|
||||
{contact.contactPhones[0] && (
|
||||
<ul className="list-inline m-0 ms-2">
|
||||
<li className="list-inline-item me-1">
|
||||
<i
|
||||
className={` ${getPhoneIcon(
|
||||
contact.contactPhones[0].label
|
||||
)} bx-xs`}
|
||||
></i>
|
||||
</li>
|
||||
<li className="list-inline-item text-small">
|
||||
{contact.contactPhones[0]?.phoneNumber}
|
||||
</li>
|
||||
</ul>
|
||||
)}
|
||||
|
||||
{contact?.tags?.length > 0 ? (
|
||||
<ul className="list-inline m-0 ms-2">
|
||||
<li className="list-inline-item me-2 my-1">
|
||||
<i className="fa-solid fa-tag fs-6 ms-1"></i>
|
||||
</li>
|
||||
{contact.tags.map((tag, index) => (
|
||||
<li key={index} className="list-inline-item text-small active">
|
||||
{tag.name}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<ul className="list-inline m-0 ms-2">
|
||||
<li className="list-inline-item me-2 my-1">
|
||||
<i className="fa-solid fa-tag fs-6 ms-1"></i>
|
||||
</li>
|
||||
<li className="list-inline-item text-small active">Other</li>
|
||||
</ul>
|
||||
)}
|
||||
|
||||
<ul className="list-inline m-0 ms-2">
|
||||
{contact?.bucketIds?.map((bucketId) => (
|
||||
<li key={bucketId} className="list-inline-item me-1">
|
||||
<span
|
||||
className="badge bg-label-primary rounded-pill d-flex align-items-center gap-1"
|
||||
style={{ padding: "0.1rem 0.3rem" }}
|
||||
>
|
||||
<i className="bx bx-pin bx-xs"></i>
|
||||
<span className="small-text">
|
||||
{getBucketNameById(data, bucketId)}
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardViewContact;
|