Compare commits

...

63 Commits

Author SHA1 Message Date
Pramod Mahajan
52a578972e Merge branch 'main' of https://git.marcoaiot.com/admin/marco.pms.web into pramod_Task#505 2025-06-19 16:48:47 +05:30
Pramod Mahajan
4cda7fdcfd added approve task feature 2025-06-19 16:44:41 +05:30
21e7ca2157 Showing proper name of project in global project dropdown when assigning project for first time 2025-06-19 14:51:10 +05:30
77f5632506 Added new project status 2025-06-19 11:42:35 +05:30
f7d0a35b3a improve singalR functionlity in assign task pop to add or remove employee if assigned or removed from project 2025-06-19 11:21:26 +05:30
ec75858f7e Resolves an issue where projects assigned to a user for the first time were not appearing in the global project selection dropdown. 2025-06-19 11:01:11 +05:30
ade3401d02 Persist project changes on multiple create/update operations.Ensures that project information changes (creation or updates) are correctly persisted when performed consecutively in the project list 2025-06-19 10:42:43 +05:30
ad25c4610e Implemented signalR in project Infrastructure management modules 2025-06-19 09:56:41 +05:30
9e4c67984c Implemented signalR in project wiget in dashboard 2025-06-18 12:26:05 +05:30
200ef5ae0a Implemented signalR in Employee module 2025-06-18 11:57:55 +05:30
6f247fb0e9 Merge branch 'SingalR_Integration' of https://git.marcoaiot.com/admin/marco.pms.web into SingalR_Integration 2025-06-17 17:19:02 +05:30
8431e856aa Added signalR functionality in Project Infrastructure 2025-06-17 17:18:59 +05:30
782ace9d1c refreshing globle project is created new project and user have manage project permission 2025-06-17 17:18:59 +05:30
fdff7f7b41 Fixed rebase conflicts 2025-06-17 17:18:59 +05:30
8832ff1c51 Implemented signalr for action assigning employee to project 2025-06-17 17:18:59 +05:30
24dc481169 Implemented signalR for create and update project 2025-06-17 17:18:28 +05:30
351e5a2b0d Implemented attendance funcationality in attendance module 2025-06-17 17:14:25 +05:30
2848177908 Change ../services/signalRService from ../services/SignalRService 2025-06-17 17:11:30 +05:30
b767641643 Avoided API calls 2025-06-17 17:11:30 +05:30
b24edba8f7 Implmeneted signalR in today's attendance 2025-06-17 17:11:29 +05:30
8217308db9 Implemented signalr for action assigning employee to project 2025-06-17 17:07:54 +05:30
ab5a598562 Implemented signalr in assign team tab in project details 2025-06-17 17:07:54 +05:30
9530f9ca87 Implemented attendance funcationality in attendance module 2025-06-17 17:07:54 +05:30
9b6fa84d0d Change ../services/signalRService from ../services/SignalRService 2025-06-17 17:07:54 +05:30
5567928c7f Avoided API calls 2025-06-17 17:07:53 +05:30
88efbf5542 Implmeneted signalR in today's attendance 2025-06-17 17:07:53 +05:30
ed2fa2d5b0 Implemented signalR for create and update project 2025-06-17 17:07:53 +05:30
a0e4b1c5ed Changed some functionallity in attendance 2025-06-17 17:07:53 +05:30
90a77d357f Implemented attendance funcationality in attendance module 2025-06-17 17:07:53 +05:30
8ad375ede4 Added lognpolling in signalR request 2025-06-17 17:07:53 +05:30
6f3a8bd7c9 Added base url in signalrServies 2025-06-17 17:07:53 +05:30
2fbf0f98ef Change ../services/signalRService from ../services/SignalRService 2025-06-17 17:07:53 +05:30
36d70b3850 Avoided API calls 2025-06-17 17:07:53 +05:30
a3381bb2e6 Implmeneted signalR in today's attendance 2025-06-17 17:07:53 +05:30
5de4953760 Added signalR functionality in Project Infrastructure 2025-06-17 17:05:33 +05:30
4b95234141 refreshing globle project is created new project and user have manage project permission 2025-06-13 12:01:36 +05:30
dde953fa08 Fixed rebase conflicts 2025-06-13 10:56:37 +05:30
967e5b4a3d Merge branch 'SingalR_Integration' of https://git.marcoaiot.com/admin/marco.pms.web into SingalR_Integration 2025-06-13 10:53:12 +05:30
d834aeddce Implemented signalr for action assigning employee to project 2025-06-13 10:53:09 +05:30
ec81377c07 Implemented signalr in assign team tab in project details 2025-06-13 10:53:08 +05:30
5611a46b95 Implemented attendance funcationality in attendance module 2025-06-13 10:50:13 +05:30
c7df297c8f Change ../services/signalRService from ../services/SignalRService 2025-06-13 10:47:21 +05:30
31ca0be50d Avoided API calls 2025-06-13 10:47:21 +05:30
b1385d2f2f Implmeneted signalR in today's attendance 2025-06-13 10:47:21 +05:30
72b7a19b69 Implemented signalr for action assigning employee to project 2025-06-13 10:43:42 +05:30
e6a461c2ab Implemented signalr in assign team tab in project details 2025-06-12 22:12:32 +05:30
250c1bf2e1 Merge branch 'SingalR_Integration' of https://git.marcoaiot.com/admin/marco.pms.web into SingalR_Integration 2025-06-12 20:47:34 +05:30
909a1f72d9 Implemented signalR for create and update project 2025-06-12 20:45:32 +05:30
73e5254fbb Changed some functionallity in attendance 2025-06-12 20:45:32 +05:30
1c0be858e9 Implemented attendance funcationality in attendance module 2025-06-12 20:45:32 +05:30
a03358dd14 Added lognpolling in signalR request 2025-06-12 20:45:32 +05:30
732a6901ee Added base url in signalrServies 2025-06-12 20:45:32 +05:30
053dfaf197 Change ../services/signalRService from ../services/SignalRService 2025-06-12 20:45:32 +05:30
b0fd14d0bb Avoided API calls 2025-06-12 20:45:32 +05:30
e349d2622c Implmeneted signalR in today's attendance 2025-06-12 20:45:31 +05:30
164f3cc02f Implemented signalR for create and update project 2025-06-12 19:39:56 +05:30
f8ee3d3a86 Changed some functionallity in attendance 2025-06-12 11:05:10 +05:30
792529776c Implemented attendance funcationality in attendance module 2025-06-11 23:23:14 +05:30
369343f1d5 Added lognpolling in signalR request 2025-06-11 15:05:27 +05:30
4cf3b391db Added base url in signalrServies 2025-06-11 12:23:39 +05:30
8682edb1d7 Change ../services/signalRService from ../services/SignalRService 2025-06-11 12:05:09 +05:30
72f0266462 Avoided API calls 2025-06-11 10:59:23 +05:30
9c8575a653 Implmeneted signalR in today's attendance 2025-06-11 10:20:53 +05:30
36 changed files with 1414 additions and 843 deletions

195
package-lock.json generated
View File

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

View File

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

View File

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

View File

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

View File

@ -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 ? (
@ -55,7 +97,7 @@ const Regularization = ({ handleRequest }) => {
<tr key={index}>
<td colSpan={2}>
<div className="d-flex justify-content-start align-items-center">
<Avatar
<Avatar
firstName={att.firstName}
lastName={att.lastName}
></Avatar>
@ -88,17 +130,22 @@ 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>
</table>
{!loading >10 && (
{!loading > 10 && (
<nav aria-label="Page ">
<ul className="pagination pagination-sm justify-content-end py-1">
<li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}>
@ -112,8 +159,9 @@ 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
className="page-link "
@ -124,8 +172,9 @@ const Regularization = ({ handleRequest }) => {
</li>
))}
<li
className={`page-item ${currentPage === totalPages ? "disabled" : ""
}`}
className={`page-item ${
currentPage === totalPages ? "disabled" : ""
}`}
>
<button
className="page-link "

View File

@ -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}`,
})
),
})
@ -46,8 +47,8 @@ const ReportTaskComments = ({
const [loading, setloading] = useState(false);
const [comments, setComment] = useState([]);
const {status, loading: auditStatusLoading} = useAuditStatus();
const [IsNeededSubTask,setIsNeededSubTask] = useState(false)
const { status, loading: auditStatusLoading } = useAuditStatus();
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">
<i className="bx bx-map me-2 bx-sm"></i>
Location :
<span className="fw-normal ms-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>
<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>
{` ${commentsData?.workItem?.activityMaster?.activityName}`}
</span>
</p>
{`${commentsData?.workItem?.workArea?.floor?.floorName}`}{" "}
<i className="bx bx-chevron-right"></i>{" "}
{`${commentsData?.workItem?.workArea?.areaName}`}{" "}
<i className="bx bx-chevron-right"></i>{" "}
{`${commentsData?.workItem?.activityMaster?.activityName}`}
</div>
</div>
<p className="fw-bold my-2 text-start">
<i className="bx bx-user me-2 bx-sm"></i>
Assigned By :
<span className=" ms-2">
{commentsData?.assignedBy?.firstName +
" " +
commentsData?.assignedBy?.lastName}
</span>{" "}
</p>
<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>
<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">
<i className="bx bx-user-check bx-lg me-1"></i>
Reported By :<span className=" ms-2">-</span>
</p>
<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>
<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>
<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}
</span>
</div>
</div>
<div className="col-12 col-sm-6">
{commentsData?.reportedDate != null && (
<div className="fw-bold my-2 text-start d-flex align-items-center">
<i className="bx bx-task me-2"></i>
<span className="me-2">Completed Work:</span>
<span className="fw-normal">
{commentsData?.completedTask}{" "}
{commentsData?.workItem?.activityMaster?.unitOfMeasurement}
</span>
</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}{" "}
{commentsData?.workItem?.activityMaster?.unitOfMeasurement}
</p>
{commentsData?.reportedDate != null && (
<p className="fw-bold my-2 text-start">
<i className="bx bx-task me-2"></i>
Completed Work : {commentsData?.completedTask}{" "}
{commentsData?.workItem?.activityMaster?.unitOfMeasurement}
</p>
)}
{!commentsData?.reportedDate && (
<p className="fw-bold my-2 text-start"> Completed Work : &nbsp;-</p>
)}
<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,16 +252,58 @@ 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 && (
<>
<p className="fw-bold m-0 text-start">
@ -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)}
>
{stat.name}
</a>
</li>
))}
</ul>
</div>
<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}
</option>
))
)}
</select>
{errors.workStatus && (
<p className="m-0 danger-text">{errors.workStatus.message}</p>
<div className="danger-text">
{errors.workStatus.message}
</div>
)}
</div>
</div>
</div>
</>
)}
<i className="bx bx-comment-detail me-2"></i>
<label className="fw-bold text-start my-1">Add comment :</label>
@ -295,17 +380,21 @@ 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"
onChange={(e)=>setIsNeededSubTask((e)=>!e)}
onChange={(e) => setIsNeededSubTask((e) => !e)}
value={IsNeededSubTask}
id="defaultCheck1"
/>
<label className="form-check-label" for="defaultCheck1">
Create Subtask
<label className="form-check-label" htmlFor="username">
Create Subtask
</label>
</div>
<span>

View File

@ -1,550 +1,228 @@
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 [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 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 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);
let payload = {
assignmentDate: new Date().toISOString(),
parentTaskId: assignData?.id,
plannedTask: data.plannedTask,
description: data.description,
taskTeam: taskTeamWithDetails,
workItemId: assignData.workItem.workItemId
}
const onSubmitForm = async (formData) => {
let payload = {
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" }}
>
&nbsp;
<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>&nbsp;
<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 ">
<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" }}
>
&nbsp;
<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>
)}
/>
</div>
{errors.plannedTask && (
<div className="danger-text mt-1">
{errors.plannedTask.message}
</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>
)}
</div>
<label
className="form-text fs-7 m-1 text-lg text-dark"
htmlFor="descriptionTextarea"
>
Description
</label>
<Controller
name="description"
control={control}
render={({ field }) => (
<textarea
{...field}
className="form-control"
id="descriptionTextarea"
rows="2"
/>
)}
/>
{errors.description && (
<div className="danger-text">{errors.description.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
</button>
<button
type="reset"
className="btn btn-sm btn-label-secondary"
data-bs-dismiss="modal"
aria-label="Close"
onClick={() => onClose()}
>
Cancel
</button>
</div>
</form>
<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"
value={activity?.workItem?.workArea?.floor?.building?.name || ""}
disabled
/>
</div>
</div>
<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>
<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>
<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
className="form-control"
rows="2"
{...register("comment")}
/>
{errors.comment && (
<div className="danger-text">{errors.comment.message}</div>
)}
</div>
<div className="col-12 text-center">
<button type="submit" className="btn btn-sm btn-primary me-2">
{isSubmitting ? "Please wait..." : "Submit"}
</button>
<button
type="button"
className="btn btn-sm btn-secondary"
onClick={() => onClose()}
disabled={isSubmitting}
>
Cancel
</button>
</div>
</form>
</div>
);
};

View File

@ -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>
@ -30,4 +55,4 @@ const Projects = () => {
);
};
export default Projects;
export default Projects;

View File

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

View File

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

View File

@ -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 && (

View File

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

View File

@ -96,7 +96,9 @@ const BuildingModel = ({
};
useEffect(() => {
setBuildings(projects_Details.data?.buildings);
if(projects_Details){
setBuildings(projects_Details.data?.buildings);
}
}, [projects_Details]);
return (
<div className="modal-dialog modal-lg modal-simple modal-edit-user">

View File

@ -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);
@ -108,12 +114,41 @@ const InfraTable = ({ buildings }) => {
);
};
useEffect(() => {
if (buildings && buildings.length > 0) {
setProjectBuilding(buildings);
setExpandedBuildings([buildings[0].id]);
}
if (buildings && buildings.length > 0) {
setProjectBuilding(buildings);
setExpandedBuildings([buildings[0].id]);
}
}, [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 && (

View File

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

View File

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

View File

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

View File

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

View File

@ -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,20 +12,20 @@ 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 { 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 { projects_Details, refetch, loading } = useProjectDetails(data?.id);
const [project, setProject] = useState(projects_Details);
const [modalConfig, setModalConfig] = useState({ type: null, data: null });
const [isModalOpen, setIsModalOpen] = useState(false);
@ -39,8 +39,8 @@ const ProjectInfra = ({
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [clearFormTrigger, setClearFormTrigger] = useState(false);
const [CurrentBuilding, setCurrentBuilding] = useState("");
const [ showModal, setShowModal ] = useState( false );
const dispatch = useDispatch()
const [showModal, setShowModal] = useState(false);
const dispatch = useDispatch();
useEffect(() => {
setProject(projects_Details);
@ -143,8 +143,8 @@ const ProjectInfra = ({
existingItem.workItemId ===
workItem.workItemId
)
? [...workArea.workItems] // Create a new array to trigger re-render
: [...workArea.workItems, workItem],
? [...workArea.workItems] // Create a new array to trigger re-render
: [...workArea.workItems, workItem],
}
: workArea
),
@ -156,15 +156,15 @@ 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,
data: updatedProject,
});
setProject( updatedProject );
setProject(updatedProject);
// closeTaskModel()
}
})
@ -173,11 +173,8 @@ const ProjectInfra = ({
});
};
const submitData = async (infraObject) => {
try
{
try {
let response = await ProjectRepository.manageProjectInfra(infraObject);
const entity = response.data;
@ -209,9 +206,8 @@ const ProjectInfra = ({
setProject((prevProject) => ({
...prevProject,
buildings: updatedBuildings,
} ) );
}));
// closeBuildingModel()
}
// Handle the floor data
else if (entity.floor) {
@ -247,12 +243,11 @@ const ProjectInfra = ({
projectId: updatedProject.id,
data: updatedProject,
});
setProject( updatedProject );
setProject(updatedProject);
// 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;
@ -291,7 +286,7 @@ const ProjectInfra = ({
projectId: updatedProject.id,
data: updatedProject,
});
setProject( updatedProject );
setProject(updatedProject);
// closeWorkAreaModel()
}
// Handle the task (workItem) data
@ -303,7 +298,6 @@ const ProjectInfra = ({
}
};
const toggleBuilding = (id) => {
setExpandedBuildings((prev) =>
prev.includes(id) ? prev.filter((bid) => bid !== id) : [...prev, id]
@ -344,15 +338,17 @@ const ProjectInfra = ({
};
const handleShow = () => setShowModal(true);
const handleClose = () => setShowModal( false );
useEffect( () =>
{
if (reloadedData)
{
refetch()
dispatch( refreshData( false ) )
}
},[reloadedData])
const handleClose = () => setShowModal(false);
useEffect(() => {
if (reloadedData) {
refetch();
dispatch(refreshData(false));
}
}, [reloadedData]);
const signalRHandler = (response) => {
setProject(response);
}
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>

View File

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

View File

@ -37,7 +37,7 @@ export const useAttendace =(projectId)=>{
}
},[projectId])
return {attendance,loading,error}
return {attendance,loading,error,recall:fetchData}
}
export const useEmployeeAttendacesLog = (id) => {

View File

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

View File

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

View File

@ -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( [] );

View File

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

View File

@ -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}
@ -231,8 +278,13 @@ const AttendancePage = () => {
showOnlyCheckout={ShowPending}
/>
</div>
{!attLoading && filteredAttendance?.length === 0 && (
<p> {ShowPending ? "No Pending Available" : "No Employee assigned yet."} </p>
{!attLoading && filteredAttendance?.length === 0 && (
<p>
{" "}
{ShowPending
? "No Pending Available"
: "No Employee assigned yet."}{" "}
</p>
)}
</>
)}
@ -254,7 +306,7 @@ const AttendancePage = () => {
</div>
)}
{attLoading && <span>Loading..</span>}
{attLoading && <span>Loading..</span>}
{!attLoading && !attendances && <span>Not Found</span>}
</div>
</div>

View File

@ -57,7 +57,7 @@ const DailyTask = () => {
const {
TaskList,
loading: task_loading,
loading: task_loading,
error: task_error,
refetch,
} = useTaskList(
@ -66,7 +66,7 @@ const DailyTask = () => {
initialized ? dateRange.endDate : null
);
const [TaskLists, setTaskLists] = useState([]);
const [TaskLists, setTaskLists] = useState([]);
const [dates, setDates] = useState([]);
const popoverRefs = useRef([]);
@ -116,7 +116,7 @@ const DailyTask = () => {
}, [TaskLists]);
const [selectedTask, selectTask] = useState(null);
const [comments, setComment] = useState({task:null,isActionAllow:false});
const [comments, setComment] = useState({ task: null, isActionAllow: false });
const [isModalOpen, setIsModalOpen] = useState(false);
const [isModalOpenComment, setIsModalOpenComment] = useState(false);
@ -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();
@ -140,16 +140,16 @@ const DailyTask = () => {
trigger: "focus",
placement: "left",
html: true,
content: el.getAttribute("data-bs-content"),
content: el.getAttribute("data-bs-content"),
});
}
});
},[dates, TaskLists]);
}, [dates, TaskLists]);
const handleProjectChange = (e) => {
const newProjectId = e.target.value;
dispatch(setProjectId(newProjectId));
setTaskLists([]);
setTaskLists([]);
setFilters({
selectedBuilding: "",
selectedFloors: [],
@ -157,24 +157,19 @@ const DailyTask = () => {
});
};
const handleCloseAction = (IsSubTask) =>
{
if ( IsSubTask )
{
setIsSubTaskNeeded( true )
setIsModalOpenComment(false)
} else
{
refetch( selectedProject, dateRange.startDate, dateRange.endDate );
setIsModalOpenComment(false)
const handleCloseAction = (IsSubTask) => {
if (IsSubTask) {
setIsSubTaskNeeded(true);
setIsModalOpenComment(false);
} else {
refetch(selectedProject, dateRange.startDate, dateRange.endDate);
setIsModalOpenComment(false);
}
}
const hanleCloseSubTask = () =>
{
setIsSubTaskNeeded( false )
setComment(null)
}
};
const hanleCloseSubTask = () => {
setIsSubTaskNeeded(false);
setComment(null);
};
return (
<>
<div
@ -189,23 +184,30 @@ const DailyTask = () => {
closeModal={closeModal}
refetch={refetch}
/>
</div>
{isModalOpenComment && (
<GlobalModel isOpen={isModalOpenComment} size="lg" closeModal={()=>setIsModalOpenComment(false)}>
<ReportTaskComments
<GlobalModel
isOpen={isModalOpenComment}
size="lg"
closeModal={() => setIsModalOpenComment(false)}
>
<ReportTaskComments
commentsData={comments.task}
actionAllow={comments.isActionAllow}
handleCloseAction={handleCloseAction}
closeModal={closeCommentModal}
/>
/>
</GlobalModel>
)}
{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>
)}
@ -227,7 +229,7 @@ const DailyTask = () => {
dateFormat="DD-MM-YYYY"
/>
<FilterIcon
taskListData={TaskList}
taskListData={TaskList}
onApplyFilters={setFilters}
currentSelectedBuilding={filters.selectedBuilding}
currentSelectedFloors={filters.selectedFloors}
@ -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>
<button
type="button"
className={`btn btn-xs btn-warning ${
task.reportedDate == null
? "d-none"
: ""
}`}
onClick={() => {
setComment({task:task,isActionAllow:true});
openComment();
}}
>
Take Action
</button>
{task.reportedDate && (
<button
type="button"
className={`btn btn-xs btn-warning ${
task.reportedDate && task.approvedBy
? "d-none"
: ""
}`}
onClick={() => {
setComment({
task: task,
isActionAllow: true,
});
openComment();
}}
>
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();
}}
>

View File

@ -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 (
<>

View File

@ -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();
@ -121,7 +122,7 @@ const ProjectDetails = () => {
case "directory": {
return (
<div className="row">
<Directory IsPage={ false} prefernceContacts={projectDetails.id} />
<Directory IsPage={false} prefernceContacts={projectDetails.id} />
</div>
);
}
@ -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 (
<>
{}

View File

@ -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();
@ -37,7 +40,7 @@ const ProjectList = () => {
const handleShow = () => setShowModal(true);
const handleClose = () => setShowModal(false);
const sortingProject = (projects) =>{
const sortingProject = (projects) => {
if (!loading && Array.isArray(projects)) {
const grouped = {};
projects.forEach((project) => {
@ -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(() => {
@ -70,16 +72,16 @@ const ProjectList = () => {
}
}, [loginUser, HasManageProjectPermission]);
const handleSubmitForm = (newProject,setloading,reset) => {
const handleSubmitForm = (newProject, setloading, reset) => {
ProjectRepository.manageProject(newProject)
.then((response) => {
const cachedProjects = getCachedData("projectslist") || [];
const updatedProjects = [...cachedProjects, response.data];
cacheData("projectslist", updatedProjects);
setProjectList( ( prev ) => [ ...prev, response.data ] );
setloading( false )
reset()
sortingProject(getCachedData("projectslist"))
setProjectList((prev) => [...prev, response.data]);
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
@ -200,49 +247,48 @@ const ProjectList = () => {
<i className="bx bx-list-ul bx-sm"></i>
</button>
</div>
<div className="dropdown ms-3">
<a
className="dropdown-toggle hide-arrow cursor-pointer"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<i className="bx bx-filter bx-lg"></i>
</a>
<ul className="dropdown-menu p-2 text-capitalize">
{[
{
id: "b74da4c2-d07e-46f2-9919-e75e49b12731",
label: "Active",
},
{
id: "603e994b-a27f-4e5d-a251-f3d69b0498ba",
label: "On Hold",
},
{
id: "ef1c356e-0fe0-42df-a5d3-8daee355492d",
label: "Inactive",
},
{
id: "33deaef9-9af1-4f2a-b443-681ea0d04f81",
label: "Completed",
},
].map(({ id, label }) => (
<li key={id}>
<div className="form-check">
<input
className="form-check-input "
type="checkbox"
checked={selectedStatuses.includes(id)}
onChange={() => handleStatusChange(id)}
/>
<label className="form-check-label">{label}</label>
</div>
</li>
))}
</ul>
</div>
<div className="dropdown ms-3">
<a
className="dropdown-toggle hide-arrow cursor-pointer"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<i className="bx bx-filter bx-lg"></i>
</a>
<ul className="dropdown-menu p-2 text-capitalize">
{[
{
id: "b74da4c2-d07e-46f2-9919-e75e49b12731",
label: "Active",
},
{
id: "603e994b-a27f-4e5d-a251-f3d69b0498ba",
label: "On Hold",
},
{
id: "ef1c356e-0fe0-42df-a5d3-8daee355492d",
label: "Inactive",
},
{
id: "33deaef9-9af1-4f2a-b443-681ea0d04f81",
label: "Completed",
},
].map(({ id, label }) => (
<li key={id}>
<div className="form-check">
<input
className="form-check-input "
type="checkbox"
checked={selectedStatuses.includes(id)}
onChange={() => handleStatusChange(id)}
/>
<label className="form-check-label">{label}</label>
</div>
</li>
))}
</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>

View File

@ -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 {

View File

@ -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
View File

@ -0,0 +1,5 @@
import EventEmitter from 'eventemitter3';
const eventBus = new EventEmitter();
export default eventBus;

View 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();
}

View File

@ -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}`;

View File

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