Compare commits
63 Commits
edcc4438a2
...
52a578972e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52a578972e | ||
|
|
4cda7fdcfd | ||
| 21e7ca2157 | |||
| 77f5632506 | |||
| f7d0a35b3a | |||
| ec75858f7e | |||
| ade3401d02 | |||
| ad25c4610e | |||
| 9e4c67984c | |||
| 200ef5ae0a | |||
| 6f247fb0e9 | |||
| 8431e856aa | |||
| 782ace9d1c | |||
| fdff7f7b41 | |||
| 8832ff1c51 | |||
| 24dc481169 | |||
| 351e5a2b0d | |||
| 2848177908 | |||
| b767641643 | |||
| b24edba8f7 | |||
| 8217308db9 | |||
| ab5a598562 | |||
| 9530f9ca87 | |||
| 9b6fa84d0d | |||
| 5567928c7f | |||
| 88efbf5542 | |||
| ed2fa2d5b0 | |||
| a0e4b1c5ed | |||
| 90a77d357f | |||
| 8ad375ede4 | |||
| 6f3a8bd7c9 | |||
| 2fbf0f98ef | |||
| 36d70b3850 | |||
| a3381bb2e6 | |||
| 5de4953760 | |||
| 4b95234141 | |||
| dde953fa08 | |||
| 967e5b4a3d | |||
| d834aeddce | |||
| ec81377c07 | |||
| 5611a46b95 | |||
| c7df297c8f | |||
| 31ca0be50d | |||
| b1385d2f2f | |||
| 72b7a19b69 | |||
| e6a461c2ab | |||
| 250c1bf2e1 | |||
| 909a1f72d9 | |||
| 73e5254fbb | |||
| 1c0be858e9 | |||
| a03358dd14 | |||
| 732a6901ee | |||
| 053dfaf197 | |||
| b0fd14d0bb | |||
| e349d2622c | |||
| 164f3cc02f | |||
| f8ee3d3a86 | |||
| 792529776c | |||
| 369343f1d5 | |||
| 4cf3b391db | |||
| 8682edb1d7 | |||
| 72f0266462 | |||
| 9c8575a653 |
195
package-lock.json
generated
195
package-lock.json
generated
@ -9,6 +9,7 @@
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.10.0",
|
||||
"@microsoft/signalr": "^8.0.7",
|
||||
"@reduxjs/toolkit": "^2.5.0",
|
||||
"@types/web": "^0.0.216",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
@ -17,6 +18,7 @@
|
||||
"axios-retry": "^4.5.0",
|
||||
"dotenv": "^16.4.7",
|
||||
"dotenv-webpack": "^8.1.0",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"localforage": "^1.10.0",
|
||||
"match-sorter": "^6.3.1",
|
||||
@ -827,6 +829,19 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@microsoft/signalr": {
|
||||
"version": "8.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-8.0.7.tgz",
|
||||
"integrity": "sha512-PHcdMv8v5hJlBkRHAuKG5trGViQEkPYee36LnJQx4xHOQ5LL4X0nEWIxOp5cCtZ7tu+30quz5V3k0b1YNuc6lw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"abort-controller": "^3.0.0",
|
||||
"eventsource": "^2.0.2",
|
||||
"fetch-cookie": "^2.0.3",
|
||||
"node-fetch": "^2.6.7",
|
||||
"ws": "^7.4.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@ -1750,6 +1765,18 @@
|
||||
"integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/abort-controller": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"event-target-shim": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.5"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.14.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
||||
@ -2980,10 +3007,19 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/event-target-shim": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
||||
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
|
||||
"integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/events": {
|
||||
@ -2996,6 +3032,15 @@
|
||||
"node": ">=0.8.x"
|
||||
}
|
||||
},
|
||||
"node_modules/eventsource": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz",
|
||||
"integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
@ -3051,6 +3096,16 @@
|
||||
"reusify": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/fetch-cookie": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-2.2.0.tgz",
|
||||
"integrity": "sha512-h9AgfjURuCgA2+2ISl8GbavpUdR+WGAM2McW/ovn4tVccegp8ZqCKWSBR8uRdM8dDNlx5WdKRWxBYUwteLDCNQ==",
|
||||
"license": "Unlicense",
|
||||
"dependencies": {
|
||||
"set-cookie-parser": "^2.4.8",
|
||||
"tough-cookie": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/file-entry-cache": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
|
||||
@ -4202,6 +4257,26 @@
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.19",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
|
||||
@ -4521,15 +4596,32 @@
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/psl": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
|
||||
"integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"punycode": "^2.3.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/lupomontero"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/querystringify": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
|
||||
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
@ -4578,6 +4670,12 @@
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/quill/node_modules/eventemitter3": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
|
||||
"integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/randombytes": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
@ -4804,6 +4902,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/requires-port": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/reselect": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||
@ -5066,6 +5170,12 @@
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
||||
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
@ -5441,6 +5551,27 @@
|
||||
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/tough-cookie": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
|
||||
"integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"psl": "^1.1.33",
|
||||
"punycode": "^2.1.1",
|
||||
"universalify": "^0.2.0",
|
||||
"url-parse": "^1.5.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
@ -5567,6 +5698,15 @@
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
|
||||
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
|
||||
@ -5605,6 +5745,16 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/url-parse": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"querystringify": "^2.1.1",
|
||||
"requires-port": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
|
||||
@ -5685,6 +5835,12 @@
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.98.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz",
|
||||
@ -5766,6 +5922,16 @@
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
@ -5903,6 +6069,27 @@
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "7.5.10",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
|
||||
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": "^5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xlsx": {
|
||||
"version": "0.18.5",
|
||||
"resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.10.0",
|
||||
"@microsoft/signalr": "^8.0.7",
|
||||
"@reduxjs/toolkit": "^2.5.0",
|
||||
"@types/web": "^0.0.216",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
@ -20,6 +21,7 @@
|
||||
"axios-retry": "^4.5.0",
|
||||
"dotenv": "^16.4.7",
|
||||
"dotenv-webpack": "^8.1.0",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"localforage": "^1.10.0",
|
||||
"match-sorter": "^6.3.1",
|
||||
|
||||
@ -6,7 +6,9 @@ import RenderAttendanceStatus from "./RenderAttendanceStatus";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { fetchAttendanceData } from "../../slices/apiSlice/attedanceLogsSlice";
|
||||
import DateRangePicker from "../common/DateRangePicker";
|
||||
import { getCachedData } from "../../slices/apiDataManager";
|
||||
import { clearCacheKey, getCachedData } from "../../slices/apiDataManager";
|
||||
import eventBus from "../../services/eventBus";
|
||||
import AttendanceRepository from "../../repositories/AttendanceRepository";
|
||||
|
||||
const usePagination = (data, itemsPerPage) => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
@ -79,7 +81,7 @@ const AttendanceLog = ({
|
||||
setIsRefreshing(false);
|
||||
}, [dateRange, projectId, dispatch, isRefreshing]);
|
||||
|
||||
useEffect(() => {
|
||||
const filtering = (data) => {
|
||||
const filteredData = showOnlyCheckout
|
||||
? data.filter((item) => item.checkOutTime === null)
|
||||
: data;
|
||||
@ -130,6 +132,10 @@ const AttendanceLog = ({
|
||||
// Create the final sorted array
|
||||
const finalData = sortedDates.flatMap((date) => groupedByDate[date]);
|
||||
setProcessedData(finalData);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
filtering(data)
|
||||
}, [data, showOnlyCheckout]);
|
||||
|
||||
const {
|
||||
@ -145,6 +151,54 @@ const AttendanceLog = ({
|
||||
resetPage();
|
||||
}, [processedData, resetPage]);
|
||||
|
||||
const handler = useCallback(
|
||||
(msg) => {
|
||||
const { startDate, endDate } = dateRange;
|
||||
const checkIn = msg.response.checkInTime.substring(0, 10);
|
||||
if (
|
||||
projectId === msg.projectId &&
|
||||
startDate <= checkIn &&
|
||||
checkIn <= endDate
|
||||
) {
|
||||
const updatedAttendance = data.map((item) =>
|
||||
item.id === msg.response.id
|
||||
? { ...item, ...msg.response }
|
||||
: item
|
||||
);
|
||||
|
||||
filtering(updatedAttendance);
|
||||
resetPage();
|
||||
}
|
||||
},
|
||||
[projectId, dateRange, data, filtering, resetPage]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
eventBus.on("attendance_log", handler);
|
||||
return () => eventBus.off("attendance_log", handler);
|
||||
}, [handler]);
|
||||
|
||||
const employeeHandler = useCallback(
|
||||
(msg) => {
|
||||
const { startDate, endDate } = dateRange;
|
||||
if (data.some((item) => item.employeeId == msg.employeeId)) {
|
||||
dispatch(
|
||||
fetchAttendanceData({
|
||||
projectId,
|
||||
fromDate: startDate,
|
||||
toDate: endDate,
|
||||
})
|
||||
)
|
||||
}
|
||||
},
|
||||
[projectId, dateRange,data]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
eventBus.on("employee", employeeHandler);
|
||||
return () => eventBus.off("employee", employeeHandler);
|
||||
}, [employeeHandler]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
|
||||
@ -19,7 +19,7 @@ import InfraTable from "../Project/Infrastructure/InfraTable";
|
||||
|
||||
const InfraPlanning = () =>
|
||||
{
|
||||
const {profile: LoggedUser} = useProfile()
|
||||
const {profile: LoggedUser, refetch : fetchData} = useProfile()
|
||||
const dispatch = useDispatch()
|
||||
const {projects,loading:project_listLoader,error:projects_error} = useProjects()
|
||||
|
||||
@ -72,7 +72,7 @@ const InfraPlanning = () =>
|
||||
|
||||
|
||||
|
||||
{(!project_deatilsLoader && projects_Details?.buildings?.length > 0) && (<InfraTable buildings={projects_Details?.buildings}/>)}
|
||||
{(!project_deatilsLoader && projects_Details?.buildings?.length > 0) && (<InfraTable buildings={projects_Details?.buildings} projectId={projects_Details.id}/>)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import Avatar from "../common/Avatar";
|
||||
import { convertShortTime } from "../../utils/dateUtils";
|
||||
import RegularizationActions from "./RegularizationActions";
|
||||
@ -6,6 +6,8 @@ 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 } from "../../slices/apiDataManager";
|
||||
|
||||
const Regularization = ({ handleRequest }) => {
|
||||
var selectedProject = useSelector((store) => store.localVariables.projectId);
|
||||
@ -22,16 +24,52 @@ const Regularization = ({ handleRequest }) => {
|
||||
const nameB = b.firstName.toLowerCase() + b.lastName.toLowerCase();
|
||||
return nameA?.localeCompare(nameB);
|
||||
};
|
||||
const filteredData = [...regularizesList]?.sort(sortByName);
|
||||
|
||||
const handler = useCallback(
|
||||
(msg) => {
|
||||
if (selectedProject == msg.projectId) {
|
||||
const updatedAttendance = regularizes?.filter( item => item.id !== msg.response.id );
|
||||
cacheData("regularizedList", {
|
||||
data: updatedAttendance,
|
||||
projectId: selectedProject,
|
||||
});
|
||||
// clearCacheKey("regularizedList")
|
||||
refetch();
|
||||
}
|
||||
},
|
||||
[selectedProject, regularizes]
|
||||
);
|
||||
|
||||
const filteredData = [...regularizesList]?.sort(sortByName);
|
||||
|
||||
const { currentPage, totalPages, currentItems, paginate } = usePagination(
|
||||
filteredData,
|
||||
10
|
||||
);
|
||||
useEffect(() => {
|
||||
eventBus.on("regularization", handler);
|
||||
return () => eventBus.off("regularization", handler);
|
||||
}, [handler]);
|
||||
|
||||
const employeeHandler = useCallback(
|
||||
(msg) => {
|
||||
if (regularizes.some((item) => item.employeeId == msg.employeeId)) {
|
||||
refetch();
|
||||
}
|
||||
},
|
||||
[regularizes]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
eventBus.on("employee", employeeHandler);
|
||||
return () => eventBus.off("employee", employeeHandler);
|
||||
}, [employeeHandler]);
|
||||
|
||||
return (
|
||||
<div className="table-responsive text-nowrap" style={{minHeight:"300px"}}>
|
||||
<div
|
||||
className="table-responsive text-nowrap"
|
||||
style={{ minHeight: "300px" }}
|
||||
>
|
||||
<table className="table mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -47,7 +85,11 @@ const Regularization = ({ handleRequest }) => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{loading && <td colSpan={6} className="text-center py-5">Loading...</td>}
|
||||
{loading && (
|
||||
<td colSpan={6} className="text-center py-5">
|
||||
Loading...
|
||||
</td>
|
||||
)}
|
||||
|
||||
{!loading &&
|
||||
(regularizes?.length > 0 ? (
|
||||
@ -88,12 +130,17 @@ const Regularization = ({ handleRequest }) => {
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={6}
|
||||
className="text-center" style={{
|
||||
<td
|
||||
colSpan={6}
|
||||
className="text-center"
|
||||
style={{
|
||||
height: "200px",
|
||||
verticalAlign: "middle",
|
||||
borderBottom: "none",
|
||||
}}>No Record Found</td>
|
||||
}}
|
||||
>
|
||||
No Record Found
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
@ -112,7 +159,8 @@ const Regularization = ({ handleRequest }) => {
|
||||
{[...Array(totalPages)].map((_, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className={`page-item ${currentPage === index + 1 ? "active" : ""
|
||||
className={`page-item ${
|
||||
currentPage === index + 1 ? "active" : ""
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
@ -124,7 +172,8 @@ const Regularization = ({ handleRequest }) => {
|
||||
</li>
|
||||
))}
|
||||
<li
|
||||
className={`page-item ${currentPage === totalPages ? "disabled" : ""
|
||||
className={`page-item ${
|
||||
currentPage === totalPages ? "disabled" : ""
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
|
||||
@ -17,6 +17,7 @@ const ReportTaskComments = ({
|
||||
actionAllow = false,
|
||||
handleCloseAction,
|
||||
}) => {
|
||||
const defaultCompletedTask = Number(commentsData?.completedTask) || 0;
|
||||
const schema = actionAllow
|
||||
? z.object({
|
||||
comment: z.string().min(1, "Comment cannot be empty"),
|
||||
@ -35,8 +36,8 @@ const ReportTaskComments = ({
|
||||
invalid_type_error: "Completed Work must be a number",
|
||||
})
|
||||
.min(0, "Completed Work must be greater than 0")
|
||||
.max(commentsData?.completedTask, {
|
||||
message: `Completed task cannot exceed : ${commentsData?.completedTask}`,
|
||||
.max(defaultCompletedTask, {
|
||||
message: `Completed task cannot exceed: ${defaultCompletedTask}`,
|
||||
})
|
||||
),
|
||||
})
|
||||
@ -47,7 +48,7 @@ const ReportTaskComments = ({
|
||||
const [loading, setloading] = useState(false);
|
||||
const [comments, setComment] = useState([]);
|
||||
const { status, loading: auditStatusLoading } = useAuditStatus();
|
||||
const [IsNeededSubTask,setIsNeededSubTask] = useState(false)
|
||||
const [IsNeededSubTask, setIsNeededSubTask] = useState(false);
|
||||
|
||||
const {
|
||||
watch,
|
||||
@ -62,7 +63,6 @@ const ReportTaskComments = ({
|
||||
|
||||
const containerRef = useRef(null);
|
||||
const firstRender = useRef(true);
|
||||
|
||||
useEffect(() => {
|
||||
const taskList = getCachedData("taskList");
|
||||
if (taskList && taskList.data && commentsData?.id) {
|
||||
@ -107,6 +107,10 @@ const ReportTaskComments = ({
|
||||
|
||||
if (actionAllow) {
|
||||
handleCloseAction(IsNeededSubTask);
|
||||
showToast(
|
||||
"Review submitted successfully. Record has been updated.",
|
||||
"success"
|
||||
);
|
||||
} else {
|
||||
if (taskList && taskList.data) {
|
||||
const updatedTaskList = taskList.data.map((task) => {
|
||||
@ -127,11 +131,11 @@ const ReportTaskComments = ({
|
||||
projectId: taskList.projectId,
|
||||
});
|
||||
}
|
||||
showToast("Successfully Sent", "success");
|
||||
}
|
||||
|
||||
reset();
|
||||
setloading(false);
|
||||
showToast("Successfully Sent", "success");
|
||||
} catch (error) {
|
||||
setloading(false);
|
||||
showToast(
|
||||
@ -142,56 +146,95 @@ const ReportTaskComments = ({
|
||||
};
|
||||
|
||||
const selectedAuditStatus = watch("workStatus");
|
||||
|
||||
useEffect(() => {
|
||||
reset({
|
||||
approvedTask: defaultCompletedTask || 0,
|
||||
});
|
||||
}, [ defaultCompletedTask ] );
|
||||
return (
|
||||
<div className="p-2 p-sm-1">
|
||||
<div className="modal-body p-sm-4 p-0">
|
||||
<h5 className=" text-center mb-2">Activity Summary</h5>
|
||||
|
||||
<p className="fw-bold my-2 text-start">
|
||||
<div className="d-flex text-start align-items-start">
|
||||
<div className="d-flex me-2">
|
||||
<i className="bx bx-map me-2 bx-sm"></i>
|
||||
Location :
|
||||
<span className="fw-normal ms-2 text-start">
|
||||
<strong>Location:</strong>
|
||||
</div>
|
||||
<div>
|
||||
{`${commentsData?.workItem?.workArea?.floor?.building?.name}`}{" "}
|
||||
<i className="bx bx-chevron-right"></i>{" "}
|
||||
{`${commentsData?.workItem?.workArea?.floor?.floorName}`}{" "}
|
||||
<i className="bx bx-chevron-right"></i>
|
||||
{`${commentsData?.workItem?.workArea?.areaName}`}
|
||||
<i className="bx bx-chevron-right"></i>
|
||||
<i className="bx bx-chevron-right"></i>{" "}
|
||||
{`${commentsData?.workItem?.workArea?.areaName}`}{" "}
|
||||
<i className="bx bx-chevron-right"></i>{" "}
|
||||
{`${commentsData?.workItem?.activityMaster?.activityName}`}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="fw-bold my-2 text-start">
|
||||
<div className="row">
|
||||
<div className="col-12 col-sm-6">
|
||||
{commentsData?.assignedBy && (
|
||||
<div className="fw-bold my-2 text-start d-flex align-items-center">
|
||||
<i className="bx bx-user me-2 bx-sm"></i>
|
||||
Assigned By :
|
||||
<span className=" ms-2">
|
||||
{commentsData?.assignedBy?.firstName +
|
||||
" " +
|
||||
commentsData?.assignedBy?.lastName}
|
||||
</span>{" "}
|
||||
</p>
|
||||
<span className="me-2">Assigned By:</span>
|
||||
<span className="fw-normal">
|
||||
{commentsData.assignedBy.firstName}{" "}
|
||||
{commentsData.assignedBy.lastName}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="fw-bold my-2 text-start">
|
||||
<div className="col-12 col-sm-6">
|
||||
{commentsData.reportedBy && (
|
||||
<div className="fw-bold text-start d-flex align-items-center">
|
||||
<i className="bx bx-user-check bx-lg me-1"></i>
|
||||
Reported By :<span className=" ms-2">-</span>
|
||||
</p>
|
||||
<span className="me-2">Reported By:</span>
|
||||
<div className="d-flex align-items-center fw-normal">
|
||||
<Avatar
|
||||
firstName={commentsData.reportedBy.firstName}
|
||||
lastName={commentsData.reportedBy.lastName}
|
||||
size="xs"
|
||||
className="me-1"
|
||||
/>
|
||||
{commentsData.reportedBy.firstName +
|
||||
" " +
|
||||
commentsData.reportedBy.lastName}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="fw-bold my-2 text-start">
|
||||
{" "}
|
||||
<i className="fa fa-tasks fs-6 me-3"></i>
|
||||
Planned Work: {commentsData?.plannedTask}{" "}
|
||||
<div className="row">
|
||||
<div className="col-12 col-sm-6">
|
||||
<div className="fw-bold my-2 text-start d-flex align-items-center">
|
||||
<i className="fa fa-tasks fs-6 me-2"></i>
|
||||
<span className="me-2">Planned Work:</span>
|
||||
<span className="fw-normal">
|
||||
{commentsData?.plannedTask}{" "}
|
||||
{commentsData?.workItem?.activityMaster?.unitOfMeasurement}
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-12 col-sm-6">
|
||||
{commentsData?.reportedDate != null && (
|
||||
<p className="fw-bold my-2 text-start">
|
||||
<div className="fw-bold my-2 text-start d-flex align-items-center">
|
||||
<i className="bx bx-task me-2"></i>
|
||||
Completed Work : {commentsData?.completedTask}{" "}
|
||||
<span className="me-2">Completed Work:</span>
|
||||
<span className="fw-normal">
|
||||
{commentsData?.completedTask}{" "}
|
||||
{commentsData?.workItem?.activityMaster?.unitOfMeasurement}
|
||||
</p>
|
||||
)}
|
||||
{!commentsData?.reportedDate && (
|
||||
<p className="fw-bold my-2 text-start"> Completed Work : -</p>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="d-flex align-items-center flex-wrap">
|
||||
<p className="fw-bold text-start m-0 me-1">
|
||||
<i className="fa-solid me-2 fs-6 fa-users-gear"></i>Team :
|
||||
@ -209,15 +252,57 @@ const ReportTaskComments = ({
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="fw-bold my-2 text-start d-flex">
|
||||
<span>
|
||||
<div className="d-flex ">
|
||||
<i className="fa-solid fa-note-sticky me-3 fs-6"></i>Note:
|
||||
</span>
|
||||
<div
|
||||
className="fw-normal ms-2"
|
||||
dangerouslySetInnerHTML={{ __html: commentsData?.description }}
|
||||
/>
|
||||
</div>
|
||||
<div className="fw-normal ms-2">{commentsData?.description}</div>
|
||||
</div>
|
||||
|
||||
|
||||
{commentsData?.approvedBy && (
|
||||
<>
|
||||
<hr className="my-1"/>
|
||||
<div className="row">
|
||||
|
||||
<div className="col-12 col-sm-6">
|
||||
{commentsData.approvedBy && (
|
||||
<div className="fw-bold text-start d-flex align-items-center">
|
||||
<i className="bx bx-user-check bx-lg me-1"></i>
|
||||
<span className="me-2">Approved By:</span>
|
||||
<div className="d-flex align-items-center fw-normal">
|
||||
<Avatar
|
||||
firstName={commentsData.approvedBy.firstName}
|
||||
lastName={commentsData.approvedBy.lastName}
|
||||
size="xs"
|
||||
className="me-1"
|
||||
/>
|
||||
{commentsData.approvedBy.firstName +
|
||||
" " +
|
||||
commentsData.approvedBy.lastName}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="col-12 col-sm-6">
|
||||
{commentsData?.workStatus != null && (
|
||||
<div className="fw-bold my-2 text-start d-flex align-items-center">
|
||||
<i className="bx bx-time-five me-2"></i>
|
||||
<span className="me-2">Work Status :</span>
|
||||
<span className="fw-normal">
|
||||
{commentsData?.workStatus.name}
|
||||
{/* {commentsData?.} */}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12 d-flex">
|
||||
<span className="fw-bold">Total Approved : </span><span className="ms-2">{commentsData?.completedTask }</span>
|
||||
</div>
|
||||
</> )}
|
||||
|
||||
{commentsData?.reportedPreSignedUrls?.length > 0 && (
|
||||
<>
|
||||
@ -234,7 +319,8 @@ const ReportTaskComments = ({
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="text-start">
|
||||
{actionAllow && (
|
||||
{( actionAllow && !commentsData.approvedBy ) && (
|
||||
<>
|
||||
<div className="row align-items-end my-1">
|
||||
<div className="col-6 col-sm-4 text-start">
|
||||
<label>Completed</label>
|
||||
@ -249,40 +335,39 @@ const ReportTaskComments = ({
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-6 col-sm-8 text-end align-items-end m-0">
|
||||
<div className="btn-group dropdown">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm btn-primary dropdown-toggle"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
>
|
||||
{selectedAuditStatus
|
||||
? status.find((s) => s.id === selectedAuditStatus)?.name
|
||||
: "Select Status"}
|
||||
</button>
|
||||
<ul className="dropdown-menu">
|
||||
{auditStatusLoading && (
|
||||
<li className="dropdown-item">Loading...</li>
|
||||
)}
|
||||
{status.map((stat) => (
|
||||
<li key={stat.id} className="py-1 cursor-pointer">
|
||||
<a
|
||||
className="dropdown-item py-1"
|
||||
onClick={() => setValue("workStatus", stat.id)}
|
||||
<div className="col-6 col-sm-4 text-center align-items-end m-0">
|
||||
<label htmlFor="workStatus" className="form-label">
|
||||
Audit Status
|
||||
</label>
|
||||
<select
|
||||
id="workStatus"
|
||||
className={`form-select form-select-sm`}
|
||||
{...register("workStatus")}
|
||||
defaultValue=""
|
||||
onChange={(e) => setValue("workStatus", e.target.value)}
|
||||
>
|
||||
<option value="" disabled>
|
||||
Select Status
|
||||
</option>
|
||||
{auditStatusLoading ? (
|
||||
<option disabled>Loading...</option>
|
||||
) : (
|
||||
status.map((stat) => (
|
||||
<option key={stat.id} value={stat.id}>
|
||||
{stat.name}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</option>
|
||||
))
|
||||
)}
|
||||
</select>
|
||||
{errors.workStatus && (
|
||||
<p className="m-0 danger-text">{errors.workStatus.message}</p>
|
||||
<div className="danger-text">
|
||||
{errors.workStatus.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</>
|
||||
)}
|
||||
<i className="bx bx-comment-detail me-2"></i>
|
||||
<label className="fw-bold text-start my-1">Add comment :</label>
|
||||
@ -295,8 +380,12 @@ const ReportTaskComments = ({
|
||||
{errors.comment && (
|
||||
<div className="danger-text">{errors.comment.message}</div>
|
||||
)}
|
||||
<div className="d-flex justify-content-between mt-2">
|
||||
<div class="form-check ">
|
||||
<div
|
||||
className={` ${
|
||||
(actionAllow && !commentsData.approvedBy)? " d-flex justify-content-between" : "text-end"
|
||||
} mt-2`}
|
||||
>
|
||||
<div className={`form-check ${!(actionAllow && !commentsData.approvedBy) && "d-none"} `}>
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
@ -304,7 +393,7 @@ const ReportTaskComments = ({
|
||||
value={IsNeededSubTask}
|
||||
id="defaultCheck1"
|
||||
/>
|
||||
<label className="form-check-label" for="defaultCheck1">
|
||||
<label className="form-check-label" htmlFor="username">
|
||||
Create Subtask
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@ -1,551 +1,229 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { changeMaster } from "../../slices/localVariablesSlice";
|
||||
import useMaster from "../../hooks/masterHook/useMaster";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { clearCacheKey, getCachedData } from "../../slices/apiDataManager";
|
||||
import { useEmployeesAllOrByProjectId } from "../../hooks/useEmployees";
|
||||
import { TasksRepository } from "../../repositories/ProjectRepository";
|
||||
import { string, z } from "zod";
|
||||
import {
|
||||
useActivitiesMaster,
|
||||
useWorkCategoriesMaster,
|
||||
} from "../../hooks/masterHook/useMaster";
|
||||
import showToast from "../../services/toastService";
|
||||
import { useProjectDetails } from "../../hooks/useProjects";
|
||||
import ProjectRepository from "../../repositories/ProjectRepository";
|
||||
import { useTaskById } from "../../hooks/useTasks";
|
||||
|
||||
const SubTask = ({ assignData, onClose }) => {
|
||||
const maxPlanned =
|
||||
assignData?.workItem?.workItem?.plannedWork -
|
||||
assignData?.workItem?.workItem?.completedWork;
|
||||
|
||||
// Zod schema for form validation
|
||||
|
||||
const schema = z.object({
|
||||
selectedEmployees: z
|
||||
.array(z.string())
|
||||
.min(1, { message: "At least one employee must be selected" }),
|
||||
description: z.string().min(1, { message: "Description is required" }),
|
||||
plannedTask: z.preprocess(
|
||||
(val) => parseInt(val, 10),
|
||||
z
|
||||
.number({
|
||||
required_error: "Planned task is required",
|
||||
invalid_type_error: "Target for Today must be a number",
|
||||
})
|
||||
.int()
|
||||
.positive({ message: "Planned task must be a positive number" })
|
||||
.max(maxPlanned, {
|
||||
message: `Planned task cannot exceed ${maxPlanned}`,
|
||||
})
|
||||
),
|
||||
const subTaskSchema = z.object({
|
||||
activityId: z.string().min(1, "Activity is required"),
|
||||
workCategoryId: z.string().min(1, "Category is required"),
|
||||
plannedWork: z.number().min(1, "Planned work is required"),
|
||||
completedWork: z.number().min(0, "Completed work cannot be negative"),
|
||||
comment: z.string().min(1, "Comment is required"),
|
||||
});
|
||||
|
||||
const [isHelpVisibleTarget, setIsHelpVisibleTarget] = useState(false);
|
||||
const helpPopupRefTarget = useRef(null);
|
||||
const [isHelpVisible, setIsHelpVisible] = useState(false);
|
||||
|
||||
const infoRef = useRef(null);
|
||||
const infoRef1 = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof bootstrap !== "undefined") {
|
||||
if (infoRef.current) {
|
||||
new bootstrap.Popover(infoRef.current, {
|
||||
trigger: "focus",
|
||||
placement: "right",
|
||||
html: true,
|
||||
content: `<div>Total Pending tasks of the Activity</div>`,
|
||||
});
|
||||
}
|
||||
|
||||
if (infoRef1.current) {
|
||||
new bootstrap.Popover(infoRef1.current, {
|
||||
trigger: "focus",
|
||||
placement: "right",
|
||||
html: true,
|
||||
content: `<div>Target task for today</div>`,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.warn("Bootstrap is not available. Popovers might not function.");
|
||||
}
|
||||
}, []);
|
||||
|
||||
const selectedProject = useSelector(
|
||||
(store) => store.localVariables.projectId
|
||||
);
|
||||
const { employees, loading: employeeLoading } = useEmployeesAllOrByProjectId(
|
||||
selectedProject,
|
||||
false
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
const { loading } = useMaster();
|
||||
const jobRoleData = getCachedData("Job Role");
|
||||
|
||||
const [selectedRole, setSelectedRole] = useState("all");
|
||||
const [displayedSelection, setDisplayedSelection] = useState("");
|
||||
|
||||
const SubTask = ({ activity, onClose }) => {
|
||||
const [selectedCategory, setSelectedCategory] = useState(null);
|
||||
const [categoryData, setCategoryData] = useState([]);
|
||||
const { activities, loading } = useActivitiesMaster();
|
||||
const { categories, categoryLoading } = useWorkCategoriesMaster();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const { Task, loading: TaskLoading } = useTaskById(activity?.id);
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
control,
|
||||
setValue,
|
||||
watch,
|
||||
formState: { errors },
|
||||
reset,
|
||||
trigger,
|
||||
setValue,
|
||||
watch,
|
||||
} = useForm({
|
||||
defaultValues: {
|
||||
selectedEmployees: [],
|
||||
description: "",
|
||||
plannedTask: "",
|
||||
},
|
||||
resolver: zodResolver(schema),
|
||||
resolver: zodResolver(subTaskSchema),
|
||||
});
|
||||
const selectedActivityId = watch("activityId");
|
||||
const selectedActivity = activities?.find((a) => a.id === selectedActivityId);
|
||||
|
||||
const handleCheckboxChange = (event, user) => {
|
||||
const isChecked = event.target.checked;
|
||||
let updatedSelectedEmployees = watch("selectedEmployees") || []; // Get current selected employees from form state
|
||||
|
||||
if (isChecked) {
|
||||
if (!updatedSelectedEmployees.includes(user.id)) {
|
||||
updatedSelectedEmployees = [...updatedSelectedEmployees, user.id];
|
||||
}
|
||||
} else {
|
||||
updatedSelectedEmployees = updatedSelectedEmployees.filter(
|
||||
(id) => id !== user.id
|
||||
);
|
||||
}
|
||||
setValue("selectedEmployees", updatedSelectedEmployees);
|
||||
trigger("selectedEmployees"); // <--- IMPORTANT: Trigger validation here
|
||||
};
|
||||
|
||||
// Set categories when fetched
|
||||
useEffect(() => {
|
||||
dispatch(changeMaster("Job Role"));
|
||||
return () => setSelectedRole("all");
|
||||
}, [dispatch]);
|
||||
setCategoryData(categories);
|
||||
}, [categories]);
|
||||
|
||||
// Handler for role filter change
|
||||
const handleRoleChange = (event) => {
|
||||
setSelectedRole(event.target.value);
|
||||
// Set initial values from activity
|
||||
useEffect(() => {
|
||||
if (Task?.workItem) {
|
||||
reset({
|
||||
workCategoryId: Task?.workItem?.workCategoryId || "",
|
||||
activityId: Task?.workItem?.activityId || "",
|
||||
plannedWork: Number(Task.notApprovedTask || Task.workItem?.plannedWork),
|
||||
completedWork: 0,
|
||||
comment: "",
|
||||
});
|
||||
}
|
||||
}, [Task, reset]);
|
||||
|
||||
const handleCategoryChange = (e) => {
|
||||
const value = e.target.value;
|
||||
const category = categoryData.find((b) => b.id === String(value));
|
||||
setSelectedCategory(category);
|
||||
setValue("workCategoryId", value);
|
||||
};
|
||||
|
||||
const filteredEmployees =
|
||||
selectedRole === "all"
|
||||
? employees
|
||||
: employees?.filter(
|
||||
(emp) => String(emp.jobRoleId || "") === selectedRole
|
||||
);
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
const selectedEmployeeIds = data.selectedEmployees;
|
||||
|
||||
const taskTeamWithDetails = selectedEmployeeIds
|
||||
.map((empId) => {
|
||||
return empId;
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
|
||||
|
||||
|
||||
const onSubmitForm = async (formData) => {
|
||||
let payload = {
|
||||
|
||||
assignmentDate: new Date().toISOString(),
|
||||
parentTaskId: assignData?.id,
|
||||
plannedTask: data.plannedTask,
|
||||
description: data.description,
|
||||
taskTeam: taskTeamWithDetails,
|
||||
workItemId: assignData.workItem.workItemId
|
||||
}
|
||||
workAreaID: Task.workItem.workAreaId,
|
||||
workCategoryId: formData.workCategoryId,
|
||||
activityID: formData.activityId,
|
||||
plannedWork: formData.plannedWork,
|
||||
completedWork: formData.completedWork,
|
||||
parentTaskId: activity?.id,
|
||||
comment: formData.comment,
|
||||
};
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
console.log(payload)
|
||||
showToast("Task Successfully Assigned", "success");
|
||||
await ProjectRepository.manageProjectTasks([payload]);
|
||||
showToast("SubTask Created Successfully", "success");
|
||||
setIsSubmitting(false);
|
||||
reset();
|
||||
clearCacheKey("projectInfo");
|
||||
|
||||
onClose();
|
||||
} catch (error) {
|
||||
console.error("Error assigning task:", error);
|
||||
showToast("Something went wrong. Please try again.", "error");
|
||||
setIsSubmitting(false);
|
||||
const msg =
|
||||
error.response.message ||
|
||||
error.message ||
|
||||
"Error occured during API calling";
|
||||
showToast(msg, "error");
|
||||
}
|
||||
};
|
||||
|
||||
const closedModel = () => {
|
||||
reset();
|
||||
onClose();
|
||||
};
|
||||
console.log(assignData);
|
||||
return (
|
||||
<div className="fs-5 text-dark text-center d-flex align-items-center justify-content-center flex-wrap">
|
||||
<p className="align-items-center flex-wrap m-0 ">Create Sub Task</p>
|
||||
<div className="container my-3">
|
||||
<div className="mb-1">
|
||||
<p className="mb-0">
|
||||
<span className="text-dark text-start d-flex align-items-center flex-wrap form-text">
|
||||
<span className="me-2 m-0 font-bold">Work Location :</span>
|
||||
{[
|
||||
assignData?.building?.name,
|
||||
assignData?.floor?.floorName,
|
||||
assignData?.workArea?.areaName,
|
||||
assignData?.workItem?.workItem?.activityMaster?.activityName,
|
||||
]
|
||||
.filter(Boolean) // Filter out any undefined/null values
|
||||
.map((item, index, array) => (
|
||||
<span key={index} className="d-flex align-items-center">
|
||||
{item}
|
||||
{index < array.length - 1 && (
|
||||
<i className="bx bx-chevron-right mx-2"></i>
|
||||
)}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="form-label text-start">
|
||||
<div className="row mb-1">
|
||||
<div className="col-12">
|
||||
<div className="form-text text-start">
|
||||
<div className="d-flex align-items-center form-text fs-7">
|
||||
<span className="text-dark">Select Team</span>
|
||||
<div className="me-2">{displayedSelection}</div>
|
||||
<a
|
||||
className="dropdown-toggle hide-arrow cursor-pointer"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<i className="bx bx-filter bx-lg text-primary"></i>
|
||||
</a>
|
||||
|
||||
<ul className="dropdown-menu p-2 text-capitalize">
|
||||
<li key="all">
|
||||
<button
|
||||
type="button"
|
||||
className="dropdown-item py-1"
|
||||
onClick={() =>
|
||||
handleRoleChange({
|
||||
target: { value: "all" },
|
||||
})
|
||||
}
|
||||
>
|
||||
All Roles
|
||||
</button>
|
||||
</li>
|
||||
{jobRoleData?.map((user) => (
|
||||
<li key={user.id}>
|
||||
<button
|
||||
type="button"
|
||||
className="dropdown-item py-1"
|
||||
value={user.id}
|
||||
onClick={handleRoleChange}
|
||||
>
|
||||
{user.name}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="col-12 h-sm-25 overflow-auto mt-2">
|
||||
{selectedRole !== "" && (
|
||||
<div className="row">
|
||||
{loading ? (
|
||||
<div className="col-12">
|
||||
<p className="text-center">Loading roles...</p>
|
||||
</div>
|
||||
) : filteredEmployees?.length > 0 ? (
|
||||
filteredEmployees?.map((emp) => {
|
||||
const jobRole = jobRoleData?.find(
|
||||
(role) => role?.id === emp?.jobRoleId
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={emp.id}
|
||||
className="col-6 col-md-4 col-lg-3 mb-3"
|
||||
>
|
||||
<div className="form-check d-flex align-items-start">
|
||||
<Controller
|
||||
name="selectedEmployees"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<input
|
||||
{...field}
|
||||
className="form-check-input me-1 mt-1"
|
||||
type="checkbox"
|
||||
id={`employee-${emp?.id}`}
|
||||
value={emp.id}
|
||||
checked={field.value?.includes(emp.id)}
|
||||
onChange={(e) => {
|
||||
handleCheckboxChange(e, emp);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div className="flex-grow-1">
|
||||
<p
|
||||
className="mb-0"
|
||||
style={{ fontSize: "13px" }}
|
||||
>
|
||||
{emp.firstName} {emp.lastName}
|
||||
</p>
|
||||
<small
|
||||
className="text-muted"
|
||||
style={{ fontSize: "11px" }}
|
||||
>
|
||||
{loading ? (
|
||||
<span className="placeholder-glow">
|
||||
<span className="placeholder col-6"></span>
|
||||
</span>
|
||||
) : (
|
||||
jobRole?.name || "Unknown Role"
|
||||
)}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div className="col-12">
|
||||
<p className="text-center">
|
||||
No employees found for the selected role.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="col-12 h-25 overflow-auto"
|
||||
style={{ maxHeight: "200px" }}
|
||||
>
|
||||
{watch("selectedEmployees")?.length > 0 && (
|
||||
<div className="mt-1">
|
||||
<div className="text-start px-2">
|
||||
{watch("selectedEmployees")?.map((empId) => {
|
||||
const emp = employees.find((emp) => emp.id === empId);
|
||||
return (
|
||||
emp && (
|
||||
<span
|
||||
key={empId}
|
||||
className="badge rounded-pill bg-label-primary d-inline-flex align-items-center me-1 mb-1"
|
||||
>
|
||||
{emp.firstName} {emp.lastName}
|
||||
<p
|
||||
type="button"
|
||||
className=" btn-close-white p-0 m-0"
|
||||
aria-label="Close"
|
||||
onClick={() => {
|
||||
const updatedSelected = watch(
|
||||
"selectedEmployees"
|
||||
).filter((id) => id !== empId);
|
||||
setValue(
|
||||
"selectedEmployees",
|
||||
updatedSelected
|
||||
);
|
||||
trigger("selectedEmployees"); // <--- IMPORTANT: Trigger validation on removing badge
|
||||
}}
|
||||
>
|
||||
<i className="icon-base bx bx-x icon-md "></i>
|
||||
</p>
|
||||
</span>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!loading && errors.selectedEmployees && (
|
||||
<div className="danger-text mt-1">
|
||||
<p>{errors.selectedEmployees.message}</p>{" "}
|
||||
{/* Use message from Zod schema */}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="col-md text-start mx-0 px-0">
|
||||
<div className="form-check form-check-inline mt-3 px-1">
|
||||
<label
|
||||
className="form-text text-dark align-items-center d-flex"
|
||||
htmlFor="inlineCheckbox1"
|
||||
>
|
||||
Pending Task of Activity :
|
||||
<label
|
||||
className="form-check-label fs-7 ms-4"
|
||||
htmlFor="inlineCheckbox1"
|
||||
>
|
||||
<strong>{assignData?.plannedTask}</strong>{" "}
|
||||
<u>
|
||||
{
|
||||
assignData?.workItem?.activityMaster
|
||||
?.unitOfMeasurement
|
||||
}
|
||||
</u>
|
||||
</label>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<div
|
||||
ref={infoRef}
|
||||
tabIndex="0"
|
||||
className="d-flex align-items-center avatar-group justify-content-center ms-2"
|
||||
data-bs-toggle="popover"
|
||||
data-bs-trigger="focus"
|
||||
data-bs-placement="right"
|
||||
data-bs-html="true"
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="13"
|
||||
height="13"
|
||||
fill="currentColor"
|
||||
className="bi bi-info-circle"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
|
||||
<path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.547 1.11l1.91-2.011c.241-.256.384-.592.287-.984-.172-.439-.58-.827-1.13-.967a.664.664 0 0 1-.58-.309l-.15-.241-.002-.002zM8 4c-.535 0-.943.372-.943.836 0 .464.408.836.943.836.535 0 .943-.372.943-.836 0-.464-.408-.836-.943-.836z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Target for Today input and validation */}
|
||||
<div className="col-md text-start mx-0 px-0">
|
||||
<div className="form-check form-check-inline mt-2 px-1 mb-2 text-start">
|
||||
<label
|
||||
className="text-dark text-start d-flex align-items-center flex-wrap form-text"
|
||||
htmlFor="inlineCheckbox1"
|
||||
>
|
||||
<span>Target for Today</span>
|
||||
<span style={{ marginLeft: "46px" }}>:</span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="form-check form-check-inline col-sm-3 mt-2"
|
||||
style={{ marginLeft: "-28px" }}
|
||||
>
|
||||
<Controller
|
||||
name="plannedTask"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<div className="d-flex align-items-center gap-1 ">
|
||||
<div className="container-xxl my-1">
|
||||
<p className="fw-semibold">Create Sub Task</p>
|
||||
<form className="row g-2" onSubmit={handleSubmit(onSubmitForm)}>
|
||||
<div className="col-6">
|
||||
<label className="form-label">Building</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-sm"
|
||||
{...field}
|
||||
id="defaultFormControlInput"
|
||||
aria-describedby="defaultFormControlHelp"
|
||||
/>
|
||||
<span style={{ paddingLeft: "6px" }}>
|
||||
{
|
||||
assignData?.workItem?.workItem?.activityMaster
|
||||
?.unitOfMeasurement
|
||||
}
|
||||
</span>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={infoRef1}
|
||||
tabIndex="0"
|
||||
className="d-flex align-items-center avatar-group justify-content-center ms-2"
|
||||
data-bs-toggle="popover"
|
||||
data-bs-trigger="focus"
|
||||
data-bs-placement="right"
|
||||
data-bs-html="true"
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="13"
|
||||
height="13"
|
||||
fill="currentColor"
|
||||
className="bi bi-info-circle"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
|
||||
<path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.547 1.11l1.91-2.011c.241-.256.384-.592.287-.984-.172-.439-.58-.827-1.13-.967a.664.664 0 0 1-.58-.309l-.15-.241-.002-.002zM8 4c-.535 0-.943.372-.943.836 0 .464.408.836.943.836.535 0 .943-.372.943-.836 0-.464-.408-.836-.943-.836z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
value={activity?.workItem?.workArea?.floor?.building?.name || ""}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
|
||||
{errors.plannedTask && (
|
||||
<div className="danger-text mt-1">
|
||||
{errors.plannedTask.message}
|
||||
<div className="col-6">
|
||||
<label className="form-label">Floor</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-sm"
|
||||
value={activity?.workItem?.workArea?.floor?.floorName || ""}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isHelpVisible && (
|
||||
<div
|
||||
className="position-absolute bg-white border p-2 rounded shadow"
|
||||
style={{ zIndex: 10, marginLeft: "10px" }}
|
||||
>
|
||||
<p className="mb-0">
|
||||
Enter the target value for today's task.
|
||||
</p>
|
||||
<div className="col-12">
|
||||
<label className="form-label">Work Area</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-sm"
|
||||
value={activity?.workItem?.workArea?.areaName || ""}
|
||||
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="">-- 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-12">
|
||||
<label className="form-label">Select Activity</label>
|
||||
<select
|
||||
className="form-select form-select-sm"
|
||||
{...register("activityId")}
|
||||
>
|
||||
<option value="">
|
||||
{categoryLoading ? "Loading..." : "--Select Activity--"}
|
||||
</option>
|
||||
{activities?.map((activity) => (
|
||||
<option key={activity.id} value={activity.id}>
|
||||
{activity.activityName}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{errors.activityId && (
|
||||
<div className="danger-text">{errors.activityId.message}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<label
|
||||
className="form-text fs-7 m-1 text-lg text-dark"
|
||||
htmlFor="descriptionTextarea"
|
||||
>
|
||||
Description
|
||||
</label>
|
||||
<Controller
|
||||
name="description"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<div className="col-4">
|
||||
<label className="form-label">Planned Work</label>
|
||||
<input
|
||||
type="number"
|
||||
className="form-control form-control-sm"
|
||||
{...register("plannedWork")}
|
||||
/>
|
||||
{errors.plannedWork && (
|
||||
<div className="danger-text">{errors.plannedWork.message}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="col-4">
|
||||
<label className="form-label">Completed Work</label>
|
||||
<input
|
||||
type="number"
|
||||
className="form-control form-control-sm"
|
||||
{...register("completedWork")}
|
||||
/>
|
||||
{errors.completedWork && (
|
||||
<div className="danger-text">{errors.completedWork.message}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="col-4">
|
||||
<label className="form-label">Unit</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-sm"
|
||||
value={selectedActivity?.unitOfMeasurement || ""}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-12">
|
||||
<label className="form-label">Comment</label>
|
||||
<textarea
|
||||
{...field}
|
||||
className="form-control"
|
||||
id="descriptionTextarea"
|
||||
rows="2"
|
||||
{...register("comment")}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.description && (
|
||||
<div className="danger-text">{errors.description.message}</div>
|
||||
{errors.comment && (
|
||||
<div className="danger-text">{errors.comment.message}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Submit and Cancel buttons */}
|
||||
<div className="col-12 d-flex justify-content-center align-items-center gap-sm-6 gap-8 text-center mt-1">
|
||||
<button type="submit" className="btn btn-sm btn-primary ">
|
||||
Submit
|
||||
<div className="col-12 text-center">
|
||||
<button type="submit" className="btn btn-sm btn-primary me-2">
|
||||
{isSubmitting ? "Please wait..." : "Submit"}
|
||||
</button>
|
||||
<button
|
||||
type="reset"
|
||||
className="btn btn-sm btn-label-secondary"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
type="button"
|
||||
className="btn btn-sm btn-secondary"
|
||||
onClick={() => onClose()}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,8 +1,33 @@
|
||||
import React from "react";
|
||||
import React, { useCallback, useEffect, useState } 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);
|
||||
|
||||
useEffect(() => {
|
||||
setProjectsData(projectsCardData);
|
||||
}, [projectsCardData]);
|
||||
|
||||
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]);
|
||||
|
||||
return (
|
||||
<div className="card p-3 h-100 text-center d-flex justify-content-between">
|
||||
@ -15,13 +40,13 @@ const Projects = () => {
|
||||
<div className="d-flex justify-content-around align-items-start mt-n2">
|
||||
<div>
|
||||
<h4 className="mb-0 fw-bold">
|
||||
{projectsCardData.totalProjects?.toLocaleString()}
|
||||
{projectData.totalProjects?.toLocaleString()}
|
||||
</h4>
|
||||
<small className="text-muted">Total</small>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="mb-0 fw-bold">
|
||||
{projectsCardData.ongoingProjects?.toLocaleString()}
|
||||
{projectData.ongoingProjects?.toLocaleString()}
|
||||
</h4>
|
||||
<small className="text-muted">Ongoing</small>
|
||||
</div>
|
||||
|
||||
@ -1,9 +1,29 @@
|
||||
import React from "react";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useDashboardTeamsCardData } from "../../hooks/useDashboard_Data";
|
||||
import eventBus from "../../services/eventBus";
|
||||
|
||||
const Teams = () => {
|
||||
const { teamsCardData } = useDashboardTeamsCardData();
|
||||
const[totalEmployees,setTotalEmployee] = useState(0);
|
||||
const[inToday,setInToday] = useState(0);
|
||||
|
||||
useEffect(() =>{
|
||||
setTotalEmployee(teamsCardData.totalEmployees)
|
||||
setInToday(teamsCardData.inToday)
|
||||
},[teamsCardData.totalEmployees,teamsCardData.inToday])
|
||||
|
||||
const handler = useCallback(
|
||||
(msg) => {
|
||||
if (msg.activity == 1) {
|
||||
setInToday(prev => prev + 1);
|
||||
}
|
||||
},
|
||||
[inToday]
|
||||
);
|
||||
useEffect(() => {
|
||||
eventBus.on("attendance", handler);
|
||||
return () => eventBus.off("attendance", handler);
|
||||
}, [handler]);
|
||||
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">
|
||||
@ -14,13 +34,13 @@ const Teams = () => {
|
||||
<div className="d-flex justify-content-around align-items-start mt-n2">
|
||||
<div>
|
||||
<h4 className="mb-0 fw-bold">
|
||||
{teamsCardData.totalEmployees?.toLocaleString()}
|
||||
{totalEmployees?.toLocaleString()}
|
||||
</h4>
|
||||
<small className="text-muted">Total Employees</small>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="mb-0 fw-bold">
|
||||
{teamsCardData.inToday?.toLocaleString()}
|
||||
{inToday?.toLocaleString()}
|
||||
</h4>
|
||||
<small className="text-muted">In Today</small>
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
import getGreetingMessage from "../../utils/greetingHandler";
|
||||
import { clearAllCache } from "../../slices/apiDataManager";
|
||||
import {
|
||||
cacheData,
|
||||
clearAllCache,
|
||||
getCachedData,
|
||||
} from "../../slices/apiDataManager";
|
||||
import AuthRepository from "../../repositories/AuthRepository";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { changeMaster, setProjectId } from "../../slices/localVariablesSlice";
|
||||
@ -9,8 +13,11 @@ import { useLocation, useNavigate, useParams } from "react-router-dom";
|
||||
import Avatar from "../../components/common/Avatar";
|
||||
import { useChangePassword } from "../Context/ChangePasswordContext";
|
||||
import { useProjects } from "../../hooks/useProjects";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useProjectName } from "../../hooks/useProjects";
|
||||
import eventBus from "../../services/eventBus";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import { MANAGE_PROJECT } from "../../utils/constants";
|
||||
|
||||
const Header = () => {
|
||||
const { profile } = useProfile();
|
||||
@ -18,6 +25,7 @@ const Header = () => {
|
||||
const dispatch = useDispatch(changeMaster("Job Role"));
|
||||
const { data, loading } = useMaster();
|
||||
const navigate = useNavigate();
|
||||
const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT);
|
||||
|
||||
const getRole = (roles, joRoleId) => {
|
||||
if (!Array.isArray(roles)) return "User";
|
||||
@ -64,7 +72,7 @@ const Header = () => {
|
||||
navigate(`/employee/${profile?.employeeInfo?.id}?for=attendance`);
|
||||
};
|
||||
// const { projects, loading: projectLoading } = useProjects();
|
||||
const { projectNames, loading: projectLoading } = useProjectName();
|
||||
const { projectNames, loading: projectLoading, fetchData } = useProjectName();
|
||||
|
||||
const selectedProject = useSelector(
|
||||
(store) => store.localVariables.projectId
|
||||
@ -85,7 +93,11 @@ const Header = () => {
|
||||
|
||||
const { openChangePassword } = useChangePassword();
|
||||
useEffect(() => {
|
||||
if (projectNames && selectedProject !== " ") {
|
||||
if (
|
||||
projectNames &&
|
||||
selectedProject !== " " &&
|
||||
!getCachedData("hasReceived")
|
||||
) {
|
||||
dispatch(setProjectId(projectNames[0]?.id));
|
||||
}
|
||||
}, [projectNames]);
|
||||
@ -93,6 +105,44 @@ const Header = () => {
|
||||
/** Check if current page id project details page */
|
||||
const isProjectPath = /^\/projects\/[a-f0-9-]{36}$/.test(location.pathname);
|
||||
|
||||
const handler = useCallback(
|
||||
async (data) => {
|
||||
if (!HasManageProjectPermission) {
|
||||
await fetchData();
|
||||
const projectExist = data.projectIds.some(
|
||||
(item) => item == selectedProject
|
||||
);
|
||||
if (projectExist) {
|
||||
cacheData("hasReceived", false);
|
||||
}
|
||||
}
|
||||
},
|
||||
[fetchData,projectNames,selectedProject]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
eventBus.on("assign_project_one", handler);
|
||||
return () => eventBus.off("assign_project_one", handler);
|
||||
}, [handler]);
|
||||
|
||||
const newProjectHandler = useCallback(
|
||||
async (msg) => {
|
||||
|
||||
if (HasManageProjectPermission && msg.keyword === "Create_Project") {
|
||||
await fetchData();
|
||||
} else if (projectNames.some((item) => item.id == msg.response.id)) {
|
||||
console.log((projectNames.some((item) => item.id == msg.response.id)))
|
||||
await fetchData();
|
||||
}
|
||||
cacheData("hasReceived", false);
|
||||
},
|
||||
[HasManageProjectPermission,projectNames]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
eventBus.on("project", newProjectHandler);
|
||||
return () => eventBus.off("project", newProjectHandler);
|
||||
}, [handler]);
|
||||
return (
|
||||
<nav
|
||||
className="layout-navbar container-xxl navbar navbar-expand-xl navbar-detached align-items-center bg-navbar-theme"
|
||||
@ -169,7 +219,6 @@ const Header = () => {
|
||||
<li className="nav-item dropdown-shortcuts navbar-dropdown dropdown me-2 me-xl-0">
|
||||
<a
|
||||
className="nav-link dropdown-toggle hide-arrow"
|
||||
|
||||
data-bs-toggle="dropdown"
|
||||
data-bs-auto-close="true"
|
||||
aria-expanded="false"
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import moment from "moment";
|
||||
import { getProjectStatusName } from "../../utils/projectStatus";
|
||||
const AboutProject = ({ data }) => {
|
||||
const [CurrentProject, setCurrentProject] = useState(data);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentProject(data);
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{data && (
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import React, { useState, useEffect, useRef, useCallback } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { changeMaster } from "../../slices/localVariablesSlice";
|
||||
import useMaster from "../../hooks/masterHook/useMaster";
|
||||
@ -10,6 +10,7 @@ import { useEmployeesAllOrByProjectId } from "../../hooks/useEmployees";
|
||||
import { TasksRepository } from "../../repositories/ProjectRepository";
|
||||
import showToast from "../../services/toastService";
|
||||
import { useProjectDetails } from "../../hooks/useProjects";
|
||||
import eventBus from "../../services/eventBus";
|
||||
|
||||
const AssignTask = ({ assignData, onClose, setAssigned }) => {
|
||||
const maxPlanned =
|
||||
@ -75,10 +76,11 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
|
||||
const selectedProject = useSelector(
|
||||
(store) => store.localVariables.projectId
|
||||
);
|
||||
const { employees, loading: employeeLoading } = useEmployeesAllOrByProjectId(
|
||||
selectedProject,
|
||||
false
|
||||
);
|
||||
const {
|
||||
employees,
|
||||
loading: employeeLoading,
|
||||
recallEmployeeData,
|
||||
} = useEmployeesAllOrByProjectId(selectedProject, false);
|
||||
const dispatch = useDispatch();
|
||||
const { loading } = useMaster(); // Assuming this is for jobRoleData loading
|
||||
const jobRoleData = getCachedData("Job Role");
|
||||
|
||||
@ -96,7 +96,9 @@ const BuildingModel = ({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if(projects_Details){
|
||||
setBuildings(projects_Details.data?.buildings);
|
||||
}
|
||||
}, [projects_Details]);
|
||||
return (
|
||||
<div className="modal-dialog modal-lg modal-simple modal-edit-user">
|
||||
|
||||
@ -1,11 +1,17 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import Building from "./Building";
|
||||
import Floor from "./Floor";
|
||||
import FloorModel from "./FloorModel";
|
||||
import showToast from "../../../services/toastService";
|
||||
import ProjectRepository from "../../../repositories/ProjectRepository";
|
||||
import eventBus from "../../../services/eventBus";
|
||||
import {
|
||||
cacheData,
|
||||
clearCacheKey,
|
||||
getCachedData,
|
||||
} from "../../../slices/apiDataManager";
|
||||
|
||||
const InfraTable = ({ buildings }) => {
|
||||
const InfraTable = ({ buildings, projectId, signalRHandler }) => {
|
||||
const [projectBuilding, setProjectBuilding] = useState([]);
|
||||
const [expandedBuildings, setExpandedBuildings] = useState([]);
|
||||
const [showFloorModal, setShowFloorModal] = useState(false);
|
||||
@ -114,6 +120,35 @@ const InfraTable = ({ buildings }) => {
|
||||
}
|
||||
}, [buildings]);
|
||||
|
||||
const handler = useCallback(
|
||||
(msg) => {
|
||||
if (msg.projectIds.some((item) => item == projectId)) {
|
||||
try {
|
||||
ProjectRepository.getProjectByprojectId(projectId)
|
||||
.then((response) => {
|
||||
cacheData("projectInfo", {
|
||||
projectId: projectId,
|
||||
data: response.data,
|
||||
});
|
||||
setProjectBuilding(response?.data?.buildings);
|
||||
signalRHandler?.(response?.data);
|
||||
showToast(msg.message, "info");
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
},
|
||||
[buildings]
|
||||
);
|
||||
useEffect(() => {
|
||||
eventBus.on("infra", handler);
|
||||
return () => eventBus.off("infra", handler);
|
||||
}, [handler]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{projectBuilding && projectBuilding.length > 0 && (
|
||||
|
||||
@ -263,7 +263,7 @@ const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => {
|
||||
<option value="b74da4c2-d07e-46f2-9919-e75e49b12731">Active</option>
|
||||
<option value="603e994b-a27f-4e5d-a251-f3d69b0498ba">On Hold</option>
|
||||
|
||||
{/* <option value="3">Suspended</option> */}
|
||||
<option value="cdad86aa-8a56-4ff4-b633-9c629057dfef">In Progress</option>
|
||||
<option value="ef1c356e-0fe0-42df-a5d3-8daee355492d">Inactive</option>
|
||||
|
||||
<option value="33deaef9-9af1-4f2a-b443-681ea0d04f81">Completed</option>
|
||||
|
||||
@ -117,7 +117,7 @@ const MapUsers = ({
|
||||
<div className="modal-dialog modal-dialog-scrollable mx-sm-auto mx-1 modal-lg modal-simple modal-edit-user">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header text-center">
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">
|
||||
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close">
|
||||
</button>
|
||||
</div>
|
||||
<p className="m-0 fw-semibold fs-5">Assign Employee</p>
|
||||
|
||||
@ -12,10 +12,15 @@ const ProjectBanner = ({ project_data }) => {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const manageProject = useHasUserPermission(MANAGE_PROJECT);
|
||||
const [CurrentProject, setCurrentProject] = useState(project_data);
|
||||
|
||||
if (project_data == null) {
|
||||
return <span>incomplete project information</span>;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentProject(project_data);
|
||||
}, [project_data]);
|
||||
|
||||
const handleShow = () => setShowModal(true);
|
||||
const handleClose = () => setShowModal(false);
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import moment from "moment";
|
||||
import { getDateDifferenceInDays } from "../../utils/dateUtils";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@ -22,6 +22,10 @@ const ProjectCard = ({ projectData, recall }) => {
|
||||
const ManageProject = useHasUserPermission(MANAGE_PROJECT);
|
||||
const [modifyProjectLoading, setMdifyProjectLoading] = useState(false);
|
||||
|
||||
useEffect(()=>{
|
||||
setProjectInfo(projectData);
|
||||
},[projectData])
|
||||
// console.log("in card view",projectInfo);
|
||||
const handleShow = async () => {
|
||||
try {
|
||||
setMdifyProjectLoading(true);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import "./ProjectInfra.css";
|
||||
import BuildingModel from "./Infrastructure/BuildingModel";
|
||||
import FloorModel from "./Infrastructure/FloorModel";
|
||||
@ -12,18 +12,18 @@ import ProjectModal from "./ProjectModal";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import { MANAGE_PROJECT_INFRA } from "../../utils/constants";
|
||||
import InfraTable from "./Infrastructure/InfraTable";
|
||||
import { cacheData, clearCacheKey, getCachedData } from "../../slices/apiDataManager";
|
||||
import {
|
||||
cacheData,
|
||||
clearCacheKey,
|
||||
getCachedData,
|
||||
} from "../../slices/apiDataManager";
|
||||
import { useProjectDetails } from "../../hooks/useProjects";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { refreshData } from "../../slices/localVariablesSlice";
|
||||
import eventBus from "../../services/eventBus";
|
||||
|
||||
const ProjectInfra = ({
|
||||
data,
|
||||
onDataChange,
|
||||
eachSiteEngineer,
|
||||
} ) =>
|
||||
{
|
||||
const reloadedData = useSelector((store)=>store.localVariables.reload)
|
||||
const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
|
||||
const reloadedData = useSelector((store) => store.localVariables.reload);
|
||||
const [expandedBuildings, setExpandedBuildings] = useState([]);
|
||||
const { projects_Details, refetch, loading } = useProjectDetails(data?.id);
|
||||
const [project, setProject] = useState(projects_Details);
|
||||
@ -40,7 +40,7 @@ const ProjectInfra = ({
|
||||
const [clearFormTrigger, setClearFormTrigger] = useState(false);
|
||||
const [CurrentBuilding, setCurrentBuilding] = useState("");
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const dispatch = useDispatch()
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
setProject(projects_Details);
|
||||
@ -156,8 +156,8 @@ const ProjectInfra = ({
|
||||
);
|
||||
updatedProject.buildings = updatedBuildings;
|
||||
// workItem update, but having local state issue there for needed to calling api
|
||||
clearCacheKey( "projectInfo" )
|
||||
refetch()
|
||||
clearCacheKey("projectInfo");
|
||||
refetch();
|
||||
|
||||
cacheData("projectInfo", {
|
||||
projectId: updatedProject.id,
|
||||
@ -173,11 +173,8 @@ const ProjectInfra = ({
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const submitData = async (infraObject) => {
|
||||
try
|
||||
{
|
||||
|
||||
try {
|
||||
let response = await ProjectRepository.manageProjectInfra(infraObject);
|
||||
const entity = response.data;
|
||||
|
||||
@ -211,7 +208,6 @@ const ProjectInfra = ({
|
||||
buildings: updatedBuildings,
|
||||
}));
|
||||
// closeBuildingModel()
|
||||
|
||||
}
|
||||
// Handle the floor data
|
||||
else if (entity.floor) {
|
||||
@ -251,8 +247,7 @@ const ProjectInfra = ({
|
||||
// closeFloorModel()
|
||||
}
|
||||
// Handle the work area data
|
||||
else if ( entity.workArea )
|
||||
{
|
||||
else if (entity.workArea) {
|
||||
let buildingId = infraObject[0].workArea.buildingId;
|
||||
|
||||
const { floorId, areaName, id } = entity.workArea;
|
||||
@ -303,7 +298,6 @@ const ProjectInfra = ({
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const toggleBuilding = (id) => {
|
||||
setExpandedBuildings((prev) =>
|
||||
prev.includes(id) ? prev.filter((bid) => bid !== id) : [...prev, id]
|
||||
@ -345,14 +339,16 @@ const ProjectInfra = ({
|
||||
|
||||
const handleShow = () => setShowModal(true);
|
||||
const handleClose = () => setShowModal(false);
|
||||
useEffect( () =>
|
||||
{
|
||||
if (reloadedData)
|
||||
{
|
||||
refetch()
|
||||
dispatch( refreshData( false ) )
|
||||
useEffect(() => {
|
||||
if (reloadedData) {
|
||||
refetch();
|
||||
dispatch(refreshData(false));
|
||||
}
|
||||
}, [reloadedData]);
|
||||
|
||||
const signalRHandler = (response) => {
|
||||
setProject(response);
|
||||
}
|
||||
},[reloadedData])
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -420,7 +416,6 @@ const ProjectInfra = ({
|
||||
>
|
||||
<TaskModel
|
||||
project={project}
|
||||
|
||||
onClose={closeTaskModel}
|
||||
onSubmit={handleTaskModelFormSubmit}
|
||||
clearTrigger={clearFormTrigger}
|
||||
@ -482,8 +477,9 @@ const ProjectInfra = ({
|
||||
{project && project.buildings?.length > 0 && (
|
||||
<InfraTable
|
||||
buildings={project?.buildings}
|
||||
project={project}
|
||||
projectId={project.id}
|
||||
handleFloor={submitData}
|
||||
signalRHandler = {signalRHandler}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import MapUsers from "./MapUsers";
|
||||
import { Link, NavLink, useNavigate } from "react-router-dom";
|
||||
|
||||
@ -13,6 +13,7 @@ import useMaster from "../../hooks/masterHook/useMaster";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import { ASSIGN_TO_PROJECT } from "../../utils/constants";
|
||||
import ConfirmModal from "../common/ConfirmModal";
|
||||
import eventBus from "../../services/eventBus";
|
||||
|
||||
const Teams = ({ project }) => {
|
||||
const dispatch = useDispatch();
|
||||
@ -173,6 +174,33 @@ const Teams = ({ project }) => {
|
||||
}
|
||||
const closeDeleteModal = ()=> setIsDeleteModal(false)
|
||||
|
||||
const handler = useCallback(
|
||||
(msg) => {
|
||||
if (msg.projectIds.some((item) => item === project.id)) {
|
||||
fetchEmployees();
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
eventBus.on("assign_project_all", handler);
|
||||
return () => eventBus.off("assign_project_all", handler);
|
||||
}, [handler]);
|
||||
|
||||
const employeeHandler = useCallback(
|
||||
(msg) => {
|
||||
if(filteredEmployees.some((item) => item.employeeId == msg.employeeId)){
|
||||
fetchEmployees();
|
||||
}
|
||||
},[filteredEmployees]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
eventBus.on("employee",employeeHandler);
|
||||
return () => eventBus.off("employee",employeeHandler)
|
||||
},[employeeHandler])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@ -322,10 +350,10 @@ const Teams = ({ project }) => {
|
||||
{" "}
|
||||
{removingEmployeeId === item.id ? (
|
||||
<div
|
||||
class="spinner-border spinner-border-sm text-primary"
|
||||
className="spinner-border spinner-border-sm text-primary"
|
||||
role="status"
|
||||
>
|
||||
<span class="visually-hidden">
|
||||
<span className="visually-hidden">
|
||||
Loading...
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -37,7 +37,7 @@ export const useAttendace =(projectId)=>{
|
||||
}
|
||||
},[projectId])
|
||||
|
||||
return {attendance,loading,error}
|
||||
return {attendance,loading,error,recall:fetchData}
|
||||
}
|
||||
|
||||
export const useEmployeeAttendacesLog = (id) => {
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import {useState,useEffect} from "react";
|
||||
import {useState,useEffect, useCallback} from "react";
|
||||
import AuthRepository from "../repositories/AuthRepository";
|
||||
import {cacheProfileData, getCachedProfileData} from "../slices/apiDataManager";
|
||||
import {cacheData, cacheProfileData, getCachedData, getCachedProfileData} from "../slices/apiDataManager";
|
||||
import {useSelector} from "react-redux";
|
||||
import eventBus from "../services/eventBus";
|
||||
|
||||
let hasFetched = false;
|
||||
let hasReceived = false;
|
||||
|
||||
export const useProfile = () => {
|
||||
const loggedUser = useSelector( ( store ) => store.globalVariables.loginUser );
|
||||
@ -24,7 +26,7 @@ export const useProfile = () => {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const validation = () => {
|
||||
if (!hasFetched) {
|
||||
hasFetched = true;
|
||||
if (!loggedUser) {
|
||||
@ -35,8 +37,26 @@ export const useProfile = () => {
|
||||
}
|
||||
|
||||
setProfile(loggedUser);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
validation();
|
||||
}, [loggedUser]);
|
||||
|
||||
const handler = useCallback(
|
||||
(data) => {
|
||||
if(!getCachedData("hasReceived")){
|
||||
cacheData("hasReceived", true);
|
||||
hasFetched = false;
|
||||
validation();
|
||||
}
|
||||
},[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
eventBus.on("assign_project_one", handler);
|
||||
return () => eventBus.off("assign_project_one", handler);
|
||||
}, [handler]);
|
||||
|
||||
return { profile, loading, error };
|
||||
};
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { cacheData, getCachedData } from "../slices/apiDataManager";
|
||||
import ProjectRepository from "../repositories/ProjectRepository";
|
||||
import { useProfile } from "./useProfile";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { setProjectId } from "../slices/localVariablesSlice";
|
||||
import EmployeeList from "../components/Directory/EmployeeList";
|
||||
import eventBus from "../services/eventBus";
|
||||
|
||||
export const useProjects = () => {
|
||||
const loggedUser = useSelector((store) => store.globalVariables.loginUser);
|
||||
@ -174,6 +175,7 @@ export const useProjectName = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [projectNames, setProjectName] = useState([]);
|
||||
const [Error, setError] = useState();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
@ -181,6 +183,9 @@ export const useProjectName = () => {
|
||||
setProjectName(response.data);
|
||||
cacheData("basicProjectNameList", response.data);
|
||||
setLoading(false);
|
||||
if(response.data.length === 1){
|
||||
dispatch(setProjectId(response.data[0]?.id));
|
||||
}
|
||||
} catch (err) {
|
||||
setError("Failed to fetch data.");
|
||||
setLoading(false);
|
||||
@ -190,5 +195,5 @@ export const useProjectName = () => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
return { projectNames, loading, Error };
|
||||
return { projectNames, loading, Error, fetchData };
|
||||
};
|
||||
|
||||
@ -43,6 +43,36 @@ export const useTaskList = (projectId, dateFrom, toDate) => {
|
||||
};
|
||||
|
||||
|
||||
export const useTaskById = (TaskId) =>
|
||||
{
|
||||
const [Task, setTask] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [ error, setError ] = useState( null );
|
||||
|
||||
|
||||
|
||||
const fetchTask = async(TaskId) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
let res = await TasksRepository.getTaskById( TaskId );
|
||||
setTask( res.data );
|
||||
|
||||
} catch ( error )
|
||||
{
|
||||
setError(err)
|
||||
}
|
||||
}
|
||||
useEffect( () =>
|
||||
{
|
||||
if ( TaskId )
|
||||
{
|
||||
fetchTask(TaskId)
|
||||
}
|
||||
}, [ TaskId ] )
|
||||
return { Task,loading}
|
||||
}
|
||||
|
||||
export const useAuditStatus = () =>
|
||||
{
|
||||
const [ status, setStatus ] = useState( [] );
|
||||
|
||||
@ -2,15 +2,26 @@ import React, { useEffect } from "react";
|
||||
import { Outlet } from "react-router-dom";
|
||||
import Header from "../components/Layout/Header";
|
||||
import Sidebar from "../components/Layout/Sidebar";
|
||||
|
||||
import { startSignalR, stopSignalR } from "../services/signalRService";
|
||||
import Footer from "../components/Layout/Footer";
|
||||
import FloatingMenu from "../components/common/FloatingMenu";
|
||||
import { FabProvider } from "../Context/FabContext";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const HomeLayout = () => {
|
||||
const loggedUser = useSelector((store) => store.globalVariables.loginUser);
|
||||
useEffect(() => {
|
||||
Main();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (loggedUser) {
|
||||
startSignalR(loggedUser);
|
||||
}
|
||||
|
||||
return () => {
|
||||
stopSignalR();
|
||||
};
|
||||
}, [loggedUser]);
|
||||
return (
|
||||
<FabProvider>
|
||||
<div className="layout-wrapper layout-content-navbar">
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import {
|
||||
cacheData,
|
||||
clearCacheKey,
|
||||
getCachedData,
|
||||
getCachedProfileData,
|
||||
} from "../../slices/apiDataManager";
|
||||
@ -18,6 +19,8 @@ import { markCurrentAttendance } from "../../slices/apiSlice/attendanceAllSlice"
|
||||
import { hasUserPermission } from "../../utils/authUtils";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import { REGULARIZE_ATTENDANCE } from "../../utils/constants";
|
||||
import eventBus from "../../services/eventBus";
|
||||
import AttendanceRepository from "../../repositories/AttendanceRepository";
|
||||
|
||||
const AttendancePage = () => {
|
||||
const [activeTab, setActiveTab] = useState("all");
|
||||
@ -25,7 +28,11 @@ const AttendancePage = () => {
|
||||
const loginUser = getCachedProfileData();
|
||||
var selectedProject = useSelector((store) => store.localVariables.projectId);
|
||||
// const { projects, loading: projectLoading } = useProjects();
|
||||
const { attendance, loading: attLoading } = useAttendace(selectedProject);
|
||||
const {
|
||||
attendance,
|
||||
loading: attLoading,
|
||||
recall: attrecall,
|
||||
} = useAttendace(selectedProject);
|
||||
const [attendances, setAttendances] = useState();
|
||||
const [empRoles, setEmpRoles] = useState(null);
|
||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||
@ -39,6 +46,40 @@ const AttendancePage = () => {
|
||||
date: new Date().toLocaleDateString(),
|
||||
});
|
||||
|
||||
const handler = useCallback(
|
||||
(msg) => {
|
||||
if (selectedProject == msg.projectId) {
|
||||
const updatedAttendance = attendances.map((item) =>
|
||||
item.employeeId === msg.response.employeeId
|
||||
? { ...item, ...msg.response }
|
||||
: item
|
||||
);
|
||||
cacheData("Attendance", {
|
||||
data: updatedAttendance,
|
||||
projectId: selectedProject,
|
||||
});
|
||||
setAttendances(updatedAttendance);
|
||||
}
|
||||
},
|
||||
[selectedProject, attrecall]
|
||||
);
|
||||
|
||||
const employeeHandler = useCallback(
|
||||
(msg) => {
|
||||
if (attendances.some((item) => item.employeeId == msg.employeeId)) {
|
||||
AttendanceRepository.getAttendance(selectedProject)
|
||||
.then((response) => {
|
||||
cacheData("Attendance", { data: response.data, selectedProject });
|
||||
setAttendances(response.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
},
|
||||
[selectedProject, attendances]
|
||||
);
|
||||
|
||||
const getRole = (roleId) => {
|
||||
if (!empRoles) return "Unassigned";
|
||||
if (!roleId) return "Unassigned";
|
||||
@ -112,9 +153,20 @@ const AttendancePage = () => {
|
||||
// )
|
||||
// : attendances;
|
||||
const filteredAttendance = ShowPending
|
||||
? attendances?.filter((att) => att?.checkInTime !== null && att?.checkOutTime === null)
|
||||
? attendances?.filter(
|
||||
(att) => att?.checkInTime !== null && att?.checkOutTime === null
|
||||
)
|
||||
: attendances;
|
||||
|
||||
useEffect(() => {
|
||||
eventBus.on("attendance", handler);
|
||||
return () => eventBus.off("attendance", handler);
|
||||
}, [handler]);
|
||||
|
||||
useEffect(() => {
|
||||
eventBus.on("employee", employeeHandler);
|
||||
return () => eventBus.off("employee", employeeHandler);
|
||||
}, [employeeHandler]);
|
||||
return (
|
||||
<>
|
||||
{isCreateModalOpen && modelConfig && (
|
||||
@ -213,15 +265,10 @@ const AttendancePage = () => {
|
||||
Regularization
|
||||
</button>
|
||||
</li>
|
||||
|
||||
|
||||
</ul>
|
||||
<div className="tab-content attedanceTabs py-0 px-1 px-sm-3">
|
||||
|
||||
|
||||
{activeTab === "all" && (
|
||||
<>
|
||||
|
||||
<div className="tab-pane fade show active py-0">
|
||||
<Attendance
|
||||
attendance={filteredAttendance}
|
||||
@ -232,7 +279,12 @@ const AttendancePage = () => {
|
||||
/>
|
||||
</div>
|
||||
{!attLoading && filteredAttendance?.length === 0 && (
|
||||
<p> {ShowPending ? "No Pending Available" : "No Employee assigned yet."} </p>
|
||||
<p>
|
||||
{" "}
|
||||
{ShowPending
|
||||
? "No Pending Available"
|
||||
: "No Employee assigned yet."}{" "}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -126,8 +126,8 @@ const DailyTask = () => {
|
||||
|
||||
const openComment = () => setIsModalOpenComment(true);
|
||||
const closeCommentModal = () => setIsModalOpenComment(false);
|
||||
const [ IsSubTaskNeeded, setIsSubTaskNeeded ] = useState( false )
|
||||
const[SubTaskData,setSubTaskData] = useState()
|
||||
const [IsSubTaskNeeded, setIsSubTaskNeeded] = useState(false);
|
||||
const [SubTaskData, setSubTaskData] = useState();
|
||||
const handletask = (task) => {
|
||||
selectTask(task);
|
||||
openModal();
|
||||
@ -157,24 +157,19 @@ const DailyTask = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleCloseAction = (IsSubTask) =>
|
||||
{
|
||||
if ( IsSubTask )
|
||||
{
|
||||
setIsSubTaskNeeded( true )
|
||||
setIsModalOpenComment(false)
|
||||
|
||||
} else
|
||||
{
|
||||
const handleCloseAction = (IsSubTask) => {
|
||||
if (IsSubTask) {
|
||||
setIsSubTaskNeeded(true);
|
||||
setIsModalOpenComment(false);
|
||||
} else {
|
||||
refetch(selectedProject, dateRange.startDate, dateRange.endDate);
|
||||
setIsModalOpenComment(false)
|
||||
}
|
||||
}
|
||||
const hanleCloseSubTask = () =>
|
||||
{
|
||||
setIsSubTaskNeeded( false )
|
||||
setComment(null)
|
||||
setIsModalOpenComment(false);
|
||||
}
|
||||
};
|
||||
const hanleCloseSubTask = () => {
|
||||
setIsSubTaskNeeded(false);
|
||||
setComment(null);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@ -189,11 +184,14 @@ const DailyTask = () => {
|
||||
closeModal={closeModal}
|
||||
refetch={refetch}
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
{isModalOpenComment && (
|
||||
<GlobalModel isOpen={isModalOpenComment} size="lg" closeModal={()=>setIsModalOpenComment(false)}>
|
||||
<GlobalModel
|
||||
isOpen={isModalOpenComment}
|
||||
size="lg"
|
||||
closeModal={() => setIsModalOpenComment(false)}
|
||||
>
|
||||
<ReportTaskComments
|
||||
commentsData={comments.task}
|
||||
actionAllow={comments.isActionAllow}
|
||||
@ -204,8 +202,12 @@ const DailyTask = () => {
|
||||
)}
|
||||
|
||||
{IsSubTaskNeeded && (
|
||||
<GlobalModel isOpen={IsSubTaskNeeded} size="lg" closeModal={hanleCloseSubTask}>
|
||||
<SubTask assignData={comments.task} onClose={hanleCloseSubTask} />
|
||||
<GlobalModel
|
||||
isOpen={IsSubTaskNeeded}
|
||||
size="lg"
|
||||
closeModal={hanleCloseSubTask}
|
||||
>
|
||||
<SubTask activity={comments.task} onClose={hanleCloseSubTask} />
|
||||
</GlobalModel>
|
||||
)}
|
||||
|
||||
@ -414,7 +416,7 @@ const DailyTask = () => {
|
||||
</div>
|
||||
</td>
|
||||
<td className="text-center">
|
||||
<div className="d-flex justify-content-center">
|
||||
<div className="d-flex justify-content-end">
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-xs btn-primary ${
|
||||
@ -429,25 +431,34 @@ const DailyTask = () => {
|
||||
>
|
||||
Report
|
||||
</button>
|
||||
{task.reportedDate && (
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-xs btn-warning ${
|
||||
task.reportedDate == null
|
||||
task.reportedDate && task.approvedBy
|
||||
? "d-none"
|
||||
: ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setComment({task:task,isActionAllow:true});
|
||||
setComment({
|
||||
task: task,
|
||||
isActionAllow: true,
|
||||
});
|
||||
openComment();
|
||||
}}
|
||||
>
|
||||
Take Action
|
||||
QC
|
||||
</button>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-xs btn-primary ms-2"
|
||||
onClick={() => {
|
||||
setComment({task:task,isActionAllow:false});
|
||||
setComment({
|
||||
task: task,
|
||||
isActionAllow: false,
|
||||
});
|
||||
openComment();
|
||||
}}
|
||||
>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import React, { useState, useEffect, useRef, useCallback } from "react";
|
||||
import moment from "moment";
|
||||
import showToast from "../../services/toastService";
|
||||
import { Link, NavLink, useNavigate } from "react-router-dom";
|
||||
@ -23,6 +23,8 @@ import EmployeeRepository from "../../repositories/EmployeeRepository";
|
||||
import ManageEmployee from "../../components/Employee/ManageEmployee";
|
||||
import ConfirmModal from "../../components/common/ConfirmModal";
|
||||
import { useSelector } from "react-redux";
|
||||
import eventBus from "../../services/eventBus";
|
||||
import { newlineChars } from "pdf-lib";
|
||||
|
||||
const EmployeeList = () => {
|
||||
const selectedProjectId = useSelector((store) => store.localVariables.projectId);
|
||||
@ -202,7 +204,6 @@ const EmployeeList = () => {
|
||||
};
|
||||
|
||||
const handleOpenDelete = (employee) => {
|
||||
console.log(employee);
|
||||
setSelectedEmpFordelete(employee);
|
||||
setIsDeleteModalOpen(true);
|
||||
};
|
||||
@ -218,6 +219,20 @@ const EmployeeList = () => {
|
||||
setSelectedProject(selectedProjectId || "");
|
||||
}, [selectedProjectId]);
|
||||
|
||||
const handler = useCallback(
|
||||
(msg) => {
|
||||
if(employees.some((item) => item.id == msg.employeeId)){
|
||||
setEmployeeList([]);
|
||||
recallEmployeeData(showInactive);
|
||||
}
|
||||
},[employees]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
eventBus.on("employee",handler);
|
||||
return () => eventBus.off("employee",handler)
|
||||
},[handler])
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
|
||||
import ActivityTimeline from "../../components/Project/ActivityTimeline";
|
||||
import ProjectOverview from "../../components/Project/ProjectOverview";
|
||||
@ -11,7 +11,7 @@ import ProjectInfra from "../../components/Project/ProjectInfra";
|
||||
import Loader from "../../components/common/Loader";
|
||||
import WorkPlan from "../../components/Project/WorkPlan";
|
||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||
import { cacheData, getCachedData } from "../../slices/apiDataManager";
|
||||
import { cacheData, clearCacheKey, getCachedData } from "../../slices/apiDataManager";
|
||||
import ProjectRepository from "../../repositories/ProjectRepository";
|
||||
import { ActivityeRepository } from "../../repositories/MastersRepository";
|
||||
import "./ProjectDetails.css";
|
||||
@ -23,6 +23,7 @@ import { useDispatch } from "react-redux";
|
||||
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||
import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
||||
import Directory from "../Directory/Directory";
|
||||
import eventBus from "../../services/eventBus";
|
||||
|
||||
const ProjectDetails = () => {
|
||||
let { projectId } = useParams();
|
||||
@ -137,6 +138,31 @@ const ProjectDetails = () => {
|
||||
setProjectDetails(projects_Details);
|
||||
}, [projects_Details, projectId]);
|
||||
|
||||
const handler = useCallback(
|
||||
(msg) => {
|
||||
if (msg.keyword === "Update_Project" && project.id === msg.response.id) {
|
||||
clearCacheKey("projectInfo")
|
||||
ProjectRepository.getProjectByprojectId(projectId)
|
||||
.then((response) => {
|
||||
setProjectDetails(response.data);
|
||||
setProject(response.data);
|
||||
cacheData("projectInfo", { projectId, data: response.data });
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
setError("Failed to fetch data.");
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
},
|
||||
[project,handleDataChange]
|
||||
);
|
||||
useEffect(() => {
|
||||
eventBus.on("project", handler);
|
||||
return () => eventBus.off("project", handler);
|
||||
}, [handler]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import ProjectCard from "../../components/Project/ProjectCard";
|
||||
import ManageProjectInfo from "../../components/Project/ManageProjectInfo";
|
||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||
@ -6,11 +6,14 @@ import ProjectRepository from "../../repositories/ProjectRepository";
|
||||
import { useProjects } from "../../hooks/useProjects";
|
||||
import { useDispatch } from "react-redux";
|
||||
import showToast from "../../services/toastService";
|
||||
import { getCachedData, cacheData } from "../../slices/apiDataManager";
|
||||
import { getCachedData, cacheData, clearCacheKey } from "../../slices/apiDataManager";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import { useProfile } from "../../hooks/useProfile";
|
||||
import { ITEMS_PER_PAGE, MANAGE_PROJECT } from "../../utils/constants";
|
||||
import ProjectListView from "./ProjectListView";
|
||||
import eventBus from "../../services/eventBus";
|
||||
import { clearApiCacheKey } from "../../slices/apiCacheSlice";
|
||||
import { defaultCheckBoxAppearanceProvider } from "pdf-lib";
|
||||
|
||||
const ProjectList = () => {
|
||||
const { profile: loginUser } = useProfile();
|
||||
@ -53,13 +56,12 @@ const ProjectList = () => {
|
||||
a.name.toLowerCase()?.localeCompare(b.name.toLowerCase())
|
||||
)
|
||||
);
|
||||
|
||||
setProjectList(sortedGrouped);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
sortingProject(projects)
|
||||
sortingProject(projects);
|
||||
}, [projects, loginUser?.projects, loading]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -77,9 +79,9 @@ const ProjectList = () => {
|
||||
const updatedProjects = [...cachedProjects, response.data];
|
||||
cacheData("projectslist", updatedProjects);
|
||||
setProjectList((prev) => [...prev, response.data]);
|
||||
setloading( false )
|
||||
reset()
|
||||
sortingProject(getCachedData("projectslist"))
|
||||
setloading(false);
|
||||
reset();
|
||||
sortingProject(getCachedData("projectslist"));
|
||||
showToast("Project Created successfully.", "success");
|
||||
setShowModal(false);
|
||||
})
|
||||
@ -123,7 +125,6 @@ const ProjectList = () => {
|
||||
indexOfLastItem
|
||||
);
|
||||
const totalPages = Math.ceil(filteredProjects.length / itemsPerPage);
|
||||
|
||||
useEffect(() => {
|
||||
const tooltipTriggerList = Array.from(
|
||||
document.querySelectorAll('[data-bs-toggle="tooltip"]')
|
||||
@ -131,6 +132,52 @@ const ProjectList = () => {
|
||||
tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el));
|
||||
}, []);
|
||||
|
||||
const handler = useCallback(
|
||||
async (msg) => {
|
||||
if (HasManageProject && msg.keyword === "Create_Project") {
|
||||
const updatedProjects = [...projectList, msg.response];
|
||||
cacheData("projectslist", updatedProjects);
|
||||
setProjectList(updatedProjects);
|
||||
sortingProject(updatedProjects);
|
||||
}
|
||||
if (
|
||||
msg.keyword === "Update_Project" &&
|
||||
projectList.some((item) => item.id === msg.response.id)
|
||||
) {
|
||||
ProjectRepository.getProjectList()
|
||||
.then((response) => {
|
||||
cacheData("projectslist", response?.data);
|
||||
sortingProject(response?.data);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
});
|
||||
}
|
||||
},
|
||||
[HasManageProject, projectList, sortingProject]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
eventBus.on("project", handler);
|
||||
return () => eventBus.off("project", handler);
|
||||
}, [handler]);
|
||||
|
||||
const assignProjectHandler = useCallback(
|
||||
async (data) => {
|
||||
clearCacheKey("projectslist");
|
||||
await refetch();
|
||||
|
||||
sortingProject(projects);
|
||||
},
|
||||
[refetch]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
eventBus.on("assign_project_one", assignProjectHandler);
|
||||
return () => eventBus.off("assign_project_one", assignProjectHandler);
|
||||
}, [handler]);
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@ -242,7 +289,6 @@ const ProjectList = () => {
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@ -341,7 +387,11 @@ const ProjectList = () => {
|
||||
</tr>
|
||||
) : (
|
||||
currentItems.map((project) => (
|
||||
<ProjectListView key={project.id} projectData={project} recall={sortingProject} />
|
||||
<ProjectListView
|
||||
key={project.id}
|
||||
projectData={project}
|
||||
recall={sortingProject}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
@ -349,7 +399,11 @@ const ProjectList = () => {
|
||||
</div>
|
||||
) : (
|
||||
currentItems.map((project) => (
|
||||
<ProjectCard key={project.id} projectData={project} recall={sortingProject} />
|
||||
<ProjectCard
|
||||
key={project.id}
|
||||
projectData={project}
|
||||
recall={sortingProject}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -21,6 +21,9 @@ const ProjectListView = ({ projectData, recall }) => {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const ManageProject = useHasUserPermission(MANAGE_PROJECT);
|
||||
useEffect(()=>{
|
||||
setProjectInfo(projectData);
|
||||
},[projectData])
|
||||
|
||||
const handleShow = async () => {
|
||||
try {
|
||||
|
||||
@ -14,12 +14,12 @@ export const TasksRepository = {
|
||||
|
||||
return api.get(url);
|
||||
},
|
||||
|
||||
getTaskById:(id)=>api.get(`/api/task/get/${id}`),
|
||||
reportTask: (data) => api.post("api/task/report", data),
|
||||
taskComments: ( data ) => api.post( "api/task/comment", data ),
|
||||
auditTask: ( data ) => api.post( '/api/task/approve', data ),
|
||||
|
||||
|
||||
auditTask: ( data ) => api.post( '/api/task/approve', data )
|
||||
|
||||
|
||||
assignTask:(data) =>api.post('/api/task/assign',data)
|
||||
|
||||
};
|
||||
|
||||
5
src/services/eventBus.js
Normal file
5
src/services/eventBus.js
Normal file
@ -0,0 +1,5 @@
|
||||
import EventEmitter from 'eventemitter3';
|
||||
|
||||
const eventBus = new EventEmitter();
|
||||
|
||||
export default eventBus;
|
||||
106
src/services/signalRService.js
Normal file
106
src/services/signalRService.js
Normal file
@ -0,0 +1,106 @@
|
||||
import * as signalR from "@microsoft/signalr";
|
||||
import {
|
||||
cacheData,
|
||||
clearCacheKey,
|
||||
getCachedData,
|
||||
} from "../slices/apiDataManager";
|
||||
import showToast from "./toastService";
|
||||
import eventBus from "./eventBus";
|
||||
import { useSelector } from "react-redux";
|
||||
import { clearApiCacheKey } from "../slices/apiCacheSlice";
|
||||
const base_Url = process.env.VITE_BASE_URL;
|
||||
// const base_Url = "https://devapi.marcoaiot.com";
|
||||
let connection = null;
|
||||
|
||||
const targetPath = "";
|
||||
|
||||
export function startSignalR(loggedUser) {
|
||||
var jwtToken = localStorage.getItem("jwtToken");
|
||||
connection = new signalR.HubConnectionBuilder()
|
||||
.withUrl(`${base_Url}/hubs/marco`, {
|
||||
accessTokenFactory: () => jwtToken,
|
||||
transport: signalR.HttpTransportType.LongPolling,
|
||||
withCredentials: false,
|
||||
})
|
||||
.withAutomaticReconnect()
|
||||
.build();
|
||||
const todayDate = new Date();
|
||||
const today = new Date(
|
||||
Date.UTC(todayDate.getFullYear(), todayDate.getMonth(), todayDate.getDate())
|
||||
)
|
||||
.toISOString()
|
||||
.split("T")[0];
|
||||
connection.on("NotificationEventHandler", (data) => {
|
||||
if (data.loggedInUserId != loggedUser?.employeeInfo.id) {
|
||||
// console.log("Notification received:", data);
|
||||
// if action taken on attendance module
|
||||
if (data.keyword == "Attendance") {
|
||||
const checkIn = data.response.checkInTime.substring(0, 10);
|
||||
if (today === checkIn) {
|
||||
eventBus.emit("attendance", data);
|
||||
}
|
||||
var onlyDate = Number(checkIn.substring(8, 10));
|
||||
|
||||
var afterTwoDay =
|
||||
checkIn.substring(0, 8) + (onlyDate + 2).toString().padStart(2, "0");
|
||||
if (
|
||||
afterTwoDay <= today &&
|
||||
(data.response.activity == 4 || data.response.activity == 5)
|
||||
) {
|
||||
eventBus.emit("regularization", data);
|
||||
}
|
||||
eventBus.emit("attendance_log", data);
|
||||
}
|
||||
|
||||
// if create or update project
|
||||
if (
|
||||
data.keyword == "Create_Project" ||
|
||||
data.keyword == "Update_Project"
|
||||
) {
|
||||
clearCacheKey("projectslist");
|
||||
eventBus.emit("project", data);
|
||||
}
|
||||
|
||||
// if assign or deassign employee to any project
|
||||
if (data.keyword == "Assign_Project") {
|
||||
if (
|
||||
data.employeeList.some((item) => item === loggedUser?.employeeInfo.id)
|
||||
) {
|
||||
try {
|
||||
cacheData("hasReceived", false);
|
||||
eventBus.emit("assign_project_one", data);
|
||||
} catch (e) {
|
||||
console.error("Error in cacheData:", e);
|
||||
}
|
||||
}
|
||||
eventBus.emit("assign_project_all", data);
|
||||
}
|
||||
// if created or updated infra
|
||||
if (data.keyword == "Infra") {
|
||||
clearCacheKey("projectInfo");
|
||||
eventBus.emit("infra", data);
|
||||
}
|
||||
|
||||
// if created or updated Employee
|
||||
if (data.keyword == "Employee") {
|
||||
clearCacheKey("employeeListByProject");
|
||||
clearCacheKey("allEmployeeList");
|
||||
clearCacheKey("allInactiveEmployeeList");
|
||||
clearCacheKey("employeeProfile");
|
||||
clearCacheKey("Attendance");
|
||||
clearCacheKey("regularizedList")
|
||||
clearCacheKey("AttendanceLogs")
|
||||
eventBus.emit("employee", data);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
connection
|
||||
.start()
|
||||
.then(() => console.log("SignalR connected"))
|
||||
.catch((err) => console.error("SignalR error:", err));
|
||||
}
|
||||
|
||||
export function stopSignalR() {
|
||||
if (connection) connection.stop();
|
||||
}
|
||||
@ -2,6 +2,7 @@ import axios from "axios";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import axiosRetry from "axios-retry";
|
||||
import showToast from "../services/toastService";
|
||||
import { startSignalR, stopSignalR } from "../services/signalRService";
|
||||
const base_Url = process.env.VITE_BASE_URL;
|
||||
// const base_Url = "https://api.marcoaiot.com";
|
||||
export const axiosClient = axios.create({
|
||||
@ -69,6 +70,8 @@ axiosClient.interceptors.response.use(
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
stopSignalR();
|
||||
|
||||
try {
|
||||
// Refresh token
|
||||
const res = await axiosClient.post("/api/Auth/refresh-token", {
|
||||
@ -82,6 +85,7 @@ axiosClient.interceptors.response.use(
|
||||
localStorage.setItem("jwtToken", token);
|
||||
localStorage.setItem("refreshToken", newRefreshToken);
|
||||
|
||||
startSignalR()
|
||||
// Set Authorization header
|
||||
originalRequest.headers["Authorization"] = `Bearer ${token}`;
|
||||
|
||||
|
||||
@ -4,8 +4,8 @@ export const getProjectStatusName = (statusId) => {
|
||||
return "Active";
|
||||
case "603e994b-a27f-4e5d-a251-f3d69b0498ba":
|
||||
return "On Hold";
|
||||
// case 3:
|
||||
// return "Suspended";
|
||||
case "cdad86aa-8a56-4ff4-b633-9c629057dfef":
|
||||
return "In Progress";
|
||||
case "ef1c356e-0fe0-42df-a5d3-8daee355492d":
|
||||
return "Inactive";
|
||||
case "33deaef9-9af1-4f2a-b443-681ea0d04f81":
|
||||
@ -23,8 +23,8 @@ export const getProjectStatusColor = (statusId) => {
|
||||
return "bg-label-info";
|
||||
case "33deaef9-9af1-4f2a-b443-681ea0d04f81":
|
||||
return "bg-label-secondary";
|
||||
case 5:
|
||||
return "bg-label-dark";
|
||||
case "cdad86aa-8a56-4ff4-b633-9c629057dfef":
|
||||
return "bg-label-success";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user