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", "version": "0.0.0",
"dependencies": { "dependencies": {
"@hookform/resolvers": "^3.10.0", "@hookform/resolvers": "^3.10.0",
"@microsoft/signalr": "^8.0.7",
"@reduxjs/toolkit": "^2.5.0", "@reduxjs/toolkit": "^2.5.0",
"@types/web": "^0.0.216", "@types/web": "^0.0.216",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
@ -17,6 +18,7 @@
"axios-retry": "^4.5.0", "axios-retry": "^4.5.0",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"dotenv-webpack": "^8.1.0", "dotenv-webpack": "^8.1.0",
"eventemitter3": "^5.0.1",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"localforage": "^1.10.0", "localforage": "^1.10.0",
"match-sorter": "^6.3.1", "match-sorter": "^6.3.1",
@ -827,6 +829,19 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@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": { "node_modules/@nodelib/fs.scandir": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -1750,6 +1765,18 @@
"integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==", "integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==",
"license": "MIT" "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": { "node_modules/acorn": {
"version": "8.14.0", "version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
@ -2980,10 +3007,19 @@
"node": ">=0.10.0" "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": { "node_modules/eventemitter3": {
"version": "2.0.3", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
"integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==", "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/events": { "node_modules/events": {
@ -2996,6 +3032,15 @@
"node": ">=0.8.x" "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": { "node_modules/extend": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@ -3051,6 +3096,16 @@
"reusify": "^1.0.4" "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": { "node_modules/file-entry-cache": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@ -4202,6 +4257,26 @@
"license": "MIT", "license": "MIT",
"peer": true "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": { "node_modules/node-releases": {
"version": "2.0.19", "version": "2.0.19",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", "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", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" "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": { "node_modules/punycode": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
"engines": { "engines": {
"node": ">=6" "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": { "node_modules/queue-microtask": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@ -4578,6 +4670,12 @@
"node": ">=0.10" "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": { "node_modules/randombytes": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@ -4804,6 +4902,12 @@
"node": ">=0.10.0" "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": { "node_modules/reselect": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
@ -5066,6 +5170,12 @@
"randombytes": "^2.1.0" "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": { "node_modules/set-function-length": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "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==", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true "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": { "node_modules/tslib": {
"version": "1.14.1", "version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
@ -5567,6 +5698,15 @@
"license": "MIT", "license": "MIT",
"peer": true "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": { "node_modules/update-browserslist-db": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
@ -5605,6 +5745,16 @@
"punycode": "^2.1.0" "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": { "node_modules/use-sync-external-store": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", "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": ">=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": { "node_modules/webpack": {
"version": "5.98.0", "version": "5.98.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz",
@ -5766,6 +5922,16 @@
"node": ">=4.0" "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": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@ -5903,6 +6069,27 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true "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": { "node_modules/xlsx": {
"version": "0.18.5", "version": "0.18.5",
"resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",

View File

@ -12,6 +12,7 @@
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^3.10.0", "@hookform/resolvers": "^3.10.0",
"@microsoft/signalr": "^8.0.7",
"@reduxjs/toolkit": "^2.5.0", "@reduxjs/toolkit": "^2.5.0",
"@types/web": "^0.0.216", "@types/web": "^0.0.216",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
@ -20,6 +21,7 @@
"axios-retry": "^4.5.0", "axios-retry": "^4.5.0",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"dotenv-webpack": "^8.1.0", "dotenv-webpack": "^8.1.0",
"eventemitter3": "^5.0.1",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"localforage": "^1.10.0", "localforage": "^1.10.0",
"match-sorter": "^6.3.1", "match-sorter": "^6.3.1",

View File

@ -6,7 +6,9 @@ import RenderAttendanceStatus from "./RenderAttendanceStatus";
import { useSelector, useDispatch } from "react-redux"; import { useSelector, useDispatch } from "react-redux";
import { fetchAttendanceData } from "../../slices/apiSlice/attedanceLogsSlice"; import { fetchAttendanceData } from "../../slices/apiSlice/attedanceLogsSlice";
import DateRangePicker from "../common/DateRangePicker"; 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 usePagination = (data, itemsPerPage) => {
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
@ -79,7 +81,7 @@ const AttendanceLog = ({
setIsRefreshing(false); setIsRefreshing(false);
}, [dateRange, projectId, dispatch, isRefreshing]); }, [dateRange, projectId, dispatch, isRefreshing]);
useEffect(() => { const filtering = (data) => {
const filteredData = showOnlyCheckout const filteredData = showOnlyCheckout
? data.filter((item) => item.checkOutTime === null) ? data.filter((item) => item.checkOutTime === null)
: data; : data;
@ -130,6 +132,10 @@ const AttendanceLog = ({
// Create the final sorted array // Create the final sorted array
const finalData = sortedDates.flatMap((date) => groupedByDate[date]); const finalData = sortedDates.flatMap((date) => groupedByDate[date]);
setProcessedData(finalData); setProcessedData(finalData);
}
useEffect(() => {
filtering(data)
}, [data, showOnlyCheckout]); }, [data, showOnlyCheckout]);
const { const {
@ -145,6 +151,54 @@ const AttendanceLog = ({
resetPage(); resetPage();
}, [processedData, 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 ( return (
<> <>
<div <div

View File

@ -19,7 +19,7 @@ import InfraTable from "../Project/Infrastructure/InfraTable";
const InfraPlanning = () => const InfraPlanning = () =>
{ {
const {profile: LoggedUser} = useProfile() const {profile: LoggedUser, refetch : fetchData} = useProfile()
const dispatch = useDispatch() const dispatch = useDispatch()
const {projects,loading:project_listLoader,error:projects_error} = useProjects() 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> </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 Avatar from "../common/Avatar";
import { convertShortTime } from "../../utils/dateUtils"; import { convertShortTime } from "../../utils/dateUtils";
import RegularizationActions from "./RegularizationActions"; import RegularizationActions from "./RegularizationActions";
@ -6,6 +6,8 @@ import { useSelector } from "react-redux";
import { useRegularizationRequests } from "../../hooks/useAttendance"; import { useRegularizationRequests } from "../../hooks/useAttendance";
import moment from "moment"; import moment from "moment";
import usePagination from "../../hooks/usePagination"; import usePagination from "../../hooks/usePagination";
import eventBus from "../../services/eventBus";
import { cacheData, clearCacheKey } from "../../slices/apiDataManager";
const Regularization = ({ handleRequest }) => { const Regularization = ({ handleRequest }) => {
var selectedProject = useSelector((store) => store.localVariables.projectId); var selectedProject = useSelector((store) => store.localVariables.projectId);
@ -22,16 +24,52 @@ const Regularization = ({ handleRequest }) => {
const nameB = b.firstName.toLowerCase() + b.lastName.toLowerCase(); const nameB = b.firstName.toLowerCase() + b.lastName.toLowerCase();
return nameA?.localeCompare(nameB); 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( const { currentPage, totalPages, currentItems, paginate } = usePagination(
filteredData, filteredData,
10 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 ( return (
<div className="table-responsive text-nowrap" style={{minHeight:"300px"}}> <div
className="table-responsive text-nowrap"
style={{ minHeight: "300px" }}
>
<table className="table mb-0"> <table className="table mb-0">
<thead> <thead>
<tr> <tr>
@ -47,7 +85,11 @@ const Regularization = ({ handleRequest }) => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{loading && <td colSpan={6} className="text-center py-5">Loading...</td>} {loading && (
<td colSpan={6} className="text-center py-5">
Loading...
</td>
)}
{!loading && {!loading &&
(regularizes?.length > 0 ? ( (regularizes?.length > 0 ? (
@ -55,7 +97,7 @@ const Regularization = ({ handleRequest }) => {
<tr key={index}> <tr key={index}>
<td colSpan={2}> <td colSpan={2}>
<div className="d-flex justify-content-start align-items-center"> <div className="d-flex justify-content-start align-items-center">
<Avatar <Avatar
firstName={att.firstName} firstName={att.firstName}
lastName={att.lastName} lastName={att.lastName}
></Avatar> ></Avatar>
@ -88,17 +130,22 @@ const Regularization = ({ handleRequest }) => {
)) ))
) : ( ) : (
<tr> <tr>
<td colSpan={6} <td
className="text-center" style={{ colSpan={6}
className="text-center"
style={{
height: "200px", height: "200px",
verticalAlign: "middle", verticalAlign: "middle",
borderBottom: "none", borderBottom: "none",
}}>No Record Found</td> }}
>
No Record Found
</td>
</tr> </tr>
))} ))}
</tbody> </tbody>
</table> </table>
{!loading >10 && ( {!loading > 10 && (
<nav aria-label="Page "> <nav aria-label="Page ">
<ul className="pagination pagination-sm justify-content-end py-1"> <ul className="pagination pagination-sm justify-content-end py-1">
<li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}> <li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}>
@ -112,8 +159,9 @@ const Regularization = ({ handleRequest }) => {
{[...Array(totalPages)].map((_, index) => ( {[...Array(totalPages)].map((_, index) => (
<li <li
key={index} key={index}
className={`page-item ${currentPage === index + 1 ? "active" : "" className={`page-item ${
}`} currentPage === index + 1 ? "active" : ""
}`}
> >
<button <button
className="page-link " className="page-link "
@ -124,8 +172,9 @@ const Regularization = ({ handleRequest }) => {
</li> </li>
))} ))}
<li <li
className={`page-item ${currentPage === totalPages ? "disabled" : "" className={`page-item ${
}`} currentPage === totalPages ? "disabled" : ""
}`}
> >
<button <button
className="page-link " className="page-link "

View File

@ -17,6 +17,7 @@ const ReportTaskComments = ({
actionAllow = false, actionAllow = false,
handleCloseAction, handleCloseAction,
}) => { }) => {
const defaultCompletedTask = Number(commentsData?.completedTask) || 0;
const schema = actionAllow const schema = actionAllow
? z.object({ ? z.object({
comment: z.string().min(1, "Comment cannot be empty"), comment: z.string().min(1, "Comment cannot be empty"),
@ -35,8 +36,8 @@ const ReportTaskComments = ({
invalid_type_error: "Completed Work must be a number", invalid_type_error: "Completed Work must be a number",
}) })
.min(0, "Completed Work must be greater than 0") .min(0, "Completed Work must be greater than 0")
.max(commentsData?.completedTask, { .max(defaultCompletedTask, {
message: `Completed task cannot exceed : ${commentsData?.completedTask}`, message: `Completed task cannot exceed: ${defaultCompletedTask}`,
}) })
), ),
}) })
@ -46,8 +47,8 @@ const ReportTaskComments = ({
const [loading, setloading] = useState(false); const [loading, setloading] = useState(false);
const [comments, setComment] = useState([]); const [comments, setComment] = useState([]);
const {status, loading: auditStatusLoading} = useAuditStatus(); const { status, loading: auditStatusLoading } = useAuditStatus();
const [IsNeededSubTask,setIsNeededSubTask] = useState(false) const [IsNeededSubTask, setIsNeededSubTask] = useState(false);
const { const {
watch, watch,
@ -62,7 +63,6 @@ const ReportTaskComments = ({
const containerRef = useRef(null); const containerRef = useRef(null);
const firstRender = useRef(true); const firstRender = useRef(true);
useEffect(() => { useEffect(() => {
const taskList = getCachedData("taskList"); const taskList = getCachedData("taskList");
if (taskList && taskList.data && commentsData?.id) { if (taskList && taskList.data && commentsData?.id) {
@ -107,6 +107,10 @@ const ReportTaskComments = ({
if (actionAllow) { if (actionAllow) {
handleCloseAction(IsNeededSubTask); handleCloseAction(IsNeededSubTask);
showToast(
"Review submitted successfully. Record has been updated.",
"success"
);
} else { } else {
if (taskList && taskList.data) { if (taskList && taskList.data) {
const updatedTaskList = taskList.data.map((task) => { const updatedTaskList = taskList.data.map((task) => {
@ -127,11 +131,11 @@ const ReportTaskComments = ({
projectId: taskList.projectId, projectId: taskList.projectId,
}); });
} }
showToast("Successfully Sent", "success");
} }
reset(); reset();
setloading(false); setloading(false);
showToast("Successfully Sent", "success");
} catch (error) { } catch (error) {
setloading(false); setloading(false);
showToast( showToast(
@ -142,56 +146,95 @@ const ReportTaskComments = ({
}; };
const selectedAuditStatus = watch("workStatus"); const selectedAuditStatus = watch("workStatus");
useEffect(() => {
reset({
approvedTask: defaultCompletedTask || 0,
});
}, [ defaultCompletedTask ] );
return ( return (
<div className="p-2 p-sm-1"> <div className="p-2 p-sm-1">
<div className="modal-body p-sm-4 p-0"> <div className="modal-body p-sm-4 p-0">
<h5 className=" text-center mb-2">Activity Summary</h5> <h5 className=" text-center mb-2">Activity Summary</h5>
<p className="fw-bold my-2 text-start"> <div className="d-flex text-start align-items-start">
<i className="bx bx-map me-2 bx-sm"></i> <div className="d-flex me-2">
Location : <i className="bx bx-map me-2 bx-sm"></i>
<span className="fw-normal ms-2 text-start"> <strong>Location:</strong>
</div>
<div>
{`${commentsData?.workItem?.workArea?.floor?.building?.name}`}{" "} {`${commentsData?.workItem?.workArea?.floor?.building?.name}`}{" "}
<i className="bx bx-chevron-right"></i>{" "} <i className="bx bx-chevron-right"></i>{" "}
{`${commentsData?.workItem?.workArea?.floor?.floorName} `}{" "} {`${commentsData?.workItem?.workArea?.floor?.floorName}`}{" "}
<i className="bx bx-chevron-right"></i> <i className="bx bx-chevron-right"></i>{" "}
{`${commentsData?.workItem?.workArea?.areaName}`} {`${commentsData?.workItem?.workArea?.areaName}`}{" "}
<i className="bx bx-chevron-right"></i> <i className="bx bx-chevron-right"></i>{" "}
{` ${commentsData?.workItem?.activityMaster?.activityName}`} {`${commentsData?.workItem?.activityMaster?.activityName}`}
</span> </div>
</p> </div>
<p className="fw-bold my-2 text-start"> <div className="row">
<i className="bx bx-user me-2 bx-sm"></i> <div className="col-12 col-sm-6">
Assigned By : {commentsData?.assignedBy && (
<span className=" ms-2"> <div className="fw-bold my-2 text-start d-flex align-items-center">
{commentsData?.assignedBy?.firstName + <i className="bx bx-user me-2 bx-sm"></i>
" " + <span className="me-2">Assigned By:</span>
commentsData?.assignedBy?.lastName} <span className="fw-normal">
</span>{" "} {commentsData.assignedBy.firstName}{" "}
</p> {commentsData.assignedBy.lastName}
</span>
</div>
)}
</div>
<p className="fw-bold my-2 text-start"> <div className="col-12 col-sm-6">
<i className="bx bx-user-check bx-lg me-1"></i> {commentsData.reportedBy && (
Reported By :<span className=" ms-2">-</span> <div className="fw-bold text-start d-flex align-items-center">
</p> <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"> <div className="d-flex align-items-center flex-wrap">
<p className="fw-bold text-start m-0 me-1"> <p className="fw-bold text-start m-0 me-1">
<i className="fa-solid me-2 fs-6 fa-users-gear"></i>Team : <i className="fa-solid me-2 fs-6 fa-users-gear"></i>Team :
@ -209,16 +252,58 @@ const ReportTaskComments = ({
))} ))}
</div> </div>
</div> </div>
<div className="fw-bold my-2 text-start d-flex"> <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: <i className="fa-solid fa-note-sticky me-3 fs-6"></i>Note:
</span> </div>
<div <div className="fw-normal ms-2">{commentsData?.description}</div>
className="fw-normal ms-2"
dangerouslySetInnerHTML={{ __html: 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 && ( {commentsData?.reportedPreSignedUrls?.length > 0 && (
<> <>
<p className="fw-bold m-0 text-start"> <p className="fw-bold m-0 text-start">
@ -234,7 +319,8 @@ const ReportTaskComments = ({
)} )}
<form onSubmit={handleSubmit(onSubmit)} className="text-start"> <form onSubmit={handleSubmit(onSubmit)} className="text-start">
{actionAllow && ( {( actionAllow && !commentsData.approvedBy ) && (
<>
<div className="row align-items-end my-1"> <div className="row align-items-end my-1">
<div className="col-6 col-sm-4 text-start"> <div className="col-6 col-sm-4 text-start">
<label>Completed</label> <label>Completed</label>
@ -249,40 +335,39 @@ const ReportTaskComments = ({
</p> </p>
)} )}
</div> </div>
<div className="col-6 col-sm-8 text-end align-items-end m-0"> <div className="col-6 col-sm-4 text-center align-items-end m-0">
<div className="btn-group dropdown"> <label htmlFor="workStatus" className="form-label">
<button Audit Status
type="button" </label>
className="btn btn-sm btn-primary dropdown-toggle" <select
data-bs-toggle="dropdown" id="workStatus"
aria-haspopup="true" className={`form-select form-select-sm`}
aria-expanded="false" {...register("workStatus")}
> defaultValue=""
{selectedAuditStatus onChange={(e) => setValue("workStatus", e.target.value)}
? status.find((s) => s.id === selectedAuditStatus)?.name >
: "Select Status"} <option value="" disabled>
</button> Select Status
<ul className="dropdown-menu"> </option>
{auditStatusLoading && ( {auditStatusLoading ? (
<li className="dropdown-item">Loading...</li> <option disabled>Loading...</option>
)} ) : (
{status.map((stat) => ( status.map((stat) => (
<li key={stat.id} className="py-1 cursor-pointer"> <option key={stat.id} value={stat.id}>
<a {stat.name}
className="dropdown-item py-1" </option>
onClick={() => setValue("workStatus", stat.id)} ))
> )}
{stat.name} </select>
</a>
</li>
))}
</ul>
</div>
{errors.workStatus && ( {errors.workStatus && (
<p className="m-0 danger-text">{errors.workStatus.message}</p> <div className="danger-text">
{errors.workStatus.message}
</div>
)} )}
</div> </div>
</div> </div>
</>
)} )}
<i className="bx bx-comment-detail me-2"></i> <i className="bx bx-comment-detail me-2"></i>
<label className="fw-bold text-start my-1">Add comment :</label> <label className="fw-bold text-start my-1">Add comment :</label>
@ -295,17 +380,21 @@ const ReportTaskComments = ({
{errors.comment && ( {errors.comment && (
<div className="danger-text">{errors.comment.message}</div> <div className="danger-text">{errors.comment.message}</div>
)} )}
<div className="d-flex justify-content-between mt-2"> <div
<div class="form-check "> className={` ${
(actionAllow && !commentsData.approvedBy)? " d-flex justify-content-between" : "text-end"
} mt-2`}
>
<div className={`form-check ${!(actionAllow && !commentsData.approvedBy) && "d-none"} `}>
<input <input
className="form-check-input" className="form-check-input"
type="checkbox" type="checkbox"
onChange={(e)=>setIsNeededSubTask((e)=>!e)} onChange={(e) => setIsNeededSubTask((e) => !e)}
value={IsNeededSubTask} value={IsNeededSubTask}
id="defaultCheck1" id="defaultCheck1"
/> />
<label className="form-check-label" for="defaultCheck1"> <label className="form-check-label" htmlFor="username">
Create Subtask Create Subtask
</label> </label>
</div> </div>
<span> <span>

View File

@ -1,550 +1,228 @@
import React, { useState, useEffect, useRef } from "react"; import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useForm } from "react-hook-form";
import { changeMaster } from "../../slices/localVariablesSlice";
import useMaster from "../../hooks/masterHook/useMaster";
import { useForm, Controller } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { clearCacheKey, getCachedData } from "../../slices/apiDataManager"; import { string, z } from "zod";
import { useEmployeesAllOrByProjectId } from "../../hooks/useEmployees"; import {
import { TasksRepository } from "../../repositories/ProjectRepository"; useActivitiesMaster,
useWorkCategoriesMaster,
} from "../../hooks/masterHook/useMaster";
import showToast from "../../services/toastService"; 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 subTaskSchema = z.object({
const maxPlanned = activityId: z.string().min(1, "Activity is required"),
assignData?.workItem?.workItem?.plannedWork - workCategoryId: z.string().min(1, "Category is required"),
assignData?.workItem?.workItem?.completedWork; plannedWork: z.number().min(1, "Planned work is required"),
completedWork: z.number().min(0, "Completed work cannot be negative"),
// Zod schema for form validation comment: z.string().min(1, "Comment is required"),
});
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 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 { const {
register,
handleSubmit, handleSubmit,
control,
setValue,
watch,
formState: { errors }, formState: { errors },
reset, reset,
trigger, setValue,
watch,
} = useForm({ } = useForm({
defaultValues: { resolver: zodResolver(subTaskSchema),
selectedEmployees: [],
description: "",
plannedTask: "",
},
resolver: zodResolver(schema),
}); });
const selectedActivityId = watch("activityId");
const selectedActivity = activities?.find((a) => a.id === selectedActivityId);
const handleCheckboxChange = (event, user) => { // Set categories when fetched
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
};
useEffect(() => { useEffect(() => {
dispatch(changeMaster("Job Role")); setCategoryData(categories);
return () => setSelectedRole("all"); }, [categories]);
}, [dispatch]);
// Handler for role filter change // Set initial values from activity
const handleRoleChange = (event) => { useEffect(() => {
setSelectedRole(event.target.value); 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 = const onSubmitForm = async (formData) => {
selectedRole === "all" let payload = {
? employees workAreaID: Task.workItem.workAreaId,
: employees?.filter( workCategoryId: formData.workCategoryId,
(emp) => String(emp.jobRoleId || "") === selectedRole activityID: formData.activityId,
); plannedWork: formData.plannedWork,
completedWork: formData.completedWork,
const onSubmit = async (data) => { parentTaskId: activity?.id,
const selectedEmployeeIds = data.selectedEmployees; comment: formData.comment,
};
const taskTeamWithDetails = selectedEmployeeIds setIsSubmitting(true);
.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
}
try { try {
console.log(payload) await ProjectRepository.manageProjectTasks([payload]);
showToast("Task Successfully Assigned", "success"); showToast("SubTask Created Successfully", "success");
setIsSubmitting(false);
reset(); reset();
clearCacheKey("projectInfo");
onClose(); onClose();
} catch (error) { } catch (error) {
console.error("Error assigning task:", error); setIsSubmitting(false);
showToast("Something went wrong. Please try again.", "error"); const msg =
error.response.message ||
error.message ||
"Error occured during API calling";
showToast(msg, "error");
} }
}; };
const closedModel = () => {
reset();
onClose();
};
console.log(assignData);
return ( return (
<div className="fs-5 text-dark text-center d-flex align-items-center justify-content-center flex-wrap"> <div className="container-xxl my-1">
<p className="align-items-center flex-wrap m-0 ">Create Sub Task</p> <p className="fw-semibold">Create Sub Task</p>
<div className="container my-3"> <form className="row g-2" onSubmit={handleSubmit(onSubmitForm)}>
<div className="mb-1"> <div className="col-6">
<p className="mb-0"> <label className="form-label">Building</label>
<span className="text-dark text-start d-flex align-items-center flex-wrap form-text"> <input
<span className="me-2 m-0 font-bold">Work Location :</span> type="text"
{[ className="form-control form-control-sm"
assignData?.building?.name, value={activity?.workItem?.workArea?.floor?.building?.name || ""}
assignData?.floor?.floorName, disabled
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> </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> </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 { useDashboardProjectsCardData } from "../../hooks/useDashboard_Data";
import eventBus from "../../services/eventBus";
import GlobalRepository from "../../repositories/GlobalRepository";
const Projects = () => { const Projects = () => {
const { projectsCardData } = useDashboardProjectsCardData(); 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 ( return (
<div className="card p-3 h-100 text-center d-flex justify-content-between"> <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 className="d-flex justify-content-around align-items-start mt-n2">
<div> <div>
<h4 className="mb-0 fw-bold"> <h4 className="mb-0 fw-bold">
{projectsCardData.totalProjects?.toLocaleString()} {projectData.totalProjects?.toLocaleString()}
</h4> </h4>
<small className="text-muted">Total</small> <small className="text-muted">Total</small>
</div> </div>
<div> <div>
<h4 className="mb-0 fw-bold"> <h4 className="mb-0 fw-bold">
{projectsCardData.ongoingProjects?.toLocaleString()} {projectData.ongoingProjects?.toLocaleString()}
</h4> </h4>
<small className="text-muted">Ongoing</small> <small className="text-muted">Ongoing</small>
</div> </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 { useDashboardTeamsCardData } from "../../hooks/useDashboard_Data";
import eventBus from "../../services/eventBus";
const Teams = () => { const Teams = () => {
const { teamsCardData } = useDashboardTeamsCardData(); 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 ( return (
<div className="card p-3 h-100 text-center d-flex justify-content-between"> <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"> <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 className="d-flex justify-content-around align-items-start mt-n2">
<div> <div>
<h4 className="mb-0 fw-bold"> <h4 className="mb-0 fw-bold">
{teamsCardData.totalEmployees?.toLocaleString()} {totalEmployees?.toLocaleString()}
</h4> </h4>
<small className="text-muted">Total Employees</small> <small className="text-muted">Total Employees</small>
</div> </div>
<div> <div>
<h4 className="mb-0 fw-bold"> <h4 className="mb-0 fw-bold">
{teamsCardData.inToday?.toLocaleString()} {inToday?.toLocaleString()}
</h4> </h4>
<small className="text-muted">In Today</small> <small className="text-muted">In Today</small>
</div> </div>

View File

@ -1,5 +1,9 @@
import getGreetingMessage from "../../utils/greetingHandler"; import getGreetingMessage from "../../utils/greetingHandler";
import { clearAllCache } from "../../slices/apiDataManager"; import {
cacheData,
clearAllCache,
getCachedData,
} from "../../slices/apiDataManager";
import AuthRepository from "../../repositories/AuthRepository"; import AuthRepository from "../../repositories/AuthRepository";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { changeMaster, setProjectId } from "../../slices/localVariablesSlice"; 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 Avatar from "../../components/common/Avatar";
import { useChangePassword } from "../Context/ChangePasswordContext"; import { useChangePassword } from "../Context/ChangePasswordContext";
import { useProjects } from "../../hooks/useProjects"; import { useProjects } from "../../hooks/useProjects";
import { useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { useProjectName } from "../../hooks/useProjects"; import { useProjectName } from "../../hooks/useProjects";
import eventBus from "../../services/eventBus";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { MANAGE_PROJECT } from "../../utils/constants";
const Header = () => { const Header = () => {
const { profile } = useProfile(); const { profile } = useProfile();
@ -18,6 +25,7 @@ const Header = () => {
const dispatch = useDispatch(changeMaster("Job Role")); const dispatch = useDispatch(changeMaster("Job Role"));
const { data, loading } = useMaster(); const { data, loading } = useMaster();
const navigate = useNavigate(); const navigate = useNavigate();
const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT);
const getRole = (roles, joRoleId) => { const getRole = (roles, joRoleId) => {
if (!Array.isArray(roles)) return "User"; if (!Array.isArray(roles)) return "User";
@ -64,7 +72,7 @@ const Header = () => {
navigate(`/employee/${profile?.employeeInfo?.id}?for=attendance`); navigate(`/employee/${profile?.employeeInfo?.id}?for=attendance`);
}; };
// const { projects, loading: projectLoading } = useProjects(); // const { projects, loading: projectLoading } = useProjects();
const { projectNames, loading: projectLoading } = useProjectName(); const { projectNames, loading: projectLoading, fetchData } = useProjectName();
const selectedProject = useSelector( const selectedProject = useSelector(
(store) => store.localVariables.projectId (store) => store.localVariables.projectId
@ -85,7 +93,11 @@ const Header = () => {
const { openChangePassword } = useChangePassword(); const { openChangePassword } = useChangePassword();
useEffect(() => { useEffect(() => {
if (projectNames && selectedProject !== " ") { if (
projectNames &&
selectedProject !== " " &&
!getCachedData("hasReceived")
) {
dispatch(setProjectId(projectNames[0]?.id)); dispatch(setProjectId(projectNames[0]?.id));
} }
}, [projectNames]); }, [projectNames]);
@ -93,6 +105,44 @@ const Header = () => {
/** Check if current page id project details page */ /** Check if current page id project details page */
const isProjectPath = /^\/projects\/[a-f0-9-]{36}$/.test(location.pathname); 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 ( return (
<nav <nav
className="layout-navbar container-xxl navbar navbar-expand-xl navbar-detached align-items-center bg-navbar-theme" 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"> <li className="nav-item dropdown-shortcuts navbar-dropdown dropdown me-2 me-xl-0">
<a <a
className="nav-link dropdown-toggle hide-arrow" className="nav-link dropdown-toggle hide-arrow"
data-bs-toggle="dropdown" data-bs-toggle="dropdown"
data-bs-auto-close="true" data-bs-auto-close="true"
aria-expanded="false" 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 moment from "moment";
import { getProjectStatusName } from "../../utils/projectStatus"; import { getProjectStatusName } from "../../utils/projectStatus";
const AboutProject = ({ data }) => { const AboutProject = ({ data }) => {
const [CurrentProject, setCurrentProject] = useState(data); const [CurrentProject, setCurrentProject] = useState(data);
useEffect(() => {
setCurrentProject(data);
}, [data]);
return ( return (
<> <>
{data && ( {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 { useDispatch, useSelector } from "react-redux";
import { changeMaster } from "../../slices/localVariablesSlice"; import { changeMaster } from "../../slices/localVariablesSlice";
import useMaster from "../../hooks/masterHook/useMaster"; import useMaster from "../../hooks/masterHook/useMaster";
@ -10,6 +10,7 @@ import { useEmployeesAllOrByProjectId } from "../../hooks/useEmployees";
import { TasksRepository } from "../../repositories/ProjectRepository"; import { TasksRepository } from "../../repositories/ProjectRepository";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
import { useProjectDetails } from "../../hooks/useProjects"; import { useProjectDetails } from "../../hooks/useProjects";
import eventBus from "../../services/eventBus";
const AssignTask = ({ assignData, onClose, setAssigned }) => { const AssignTask = ({ assignData, onClose, setAssigned }) => {
const maxPlanned = const maxPlanned =
@ -75,10 +76,11 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
const selectedProject = useSelector( const selectedProject = useSelector(
(store) => store.localVariables.projectId (store) => store.localVariables.projectId
); );
const { employees, loading: employeeLoading } = useEmployeesAllOrByProjectId( const {
selectedProject, employees,
false loading: employeeLoading,
); recallEmployeeData,
} = useEmployeesAllOrByProjectId(selectedProject, false);
const dispatch = useDispatch(); const dispatch = useDispatch();
const { loading } = useMaster(); // Assuming this is for jobRoleData loading const { loading } = useMaster(); // Assuming this is for jobRoleData loading
const jobRoleData = getCachedData("Job Role"); const jobRoleData = getCachedData("Job Role");

View File

@ -96,7 +96,9 @@ const BuildingModel = ({
}; };
useEffect(() => { useEffect(() => {
setBuildings(projects_Details.data?.buildings); if(projects_Details){
setBuildings(projects_Details.data?.buildings);
}
}, [projects_Details]); }, [projects_Details]);
return ( return (
<div className="modal-dialog modal-lg modal-simple modal-edit-user"> <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 Building from "./Building";
import Floor from "./Floor"; import Floor from "./Floor";
import FloorModel from "./FloorModel"; import FloorModel from "./FloorModel";
import showToast from "../../../services/toastService"; import showToast from "../../../services/toastService";
import ProjectRepository from "../../../repositories/ProjectRepository"; 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 [projectBuilding, setProjectBuilding] = useState([]);
const [expandedBuildings, setExpandedBuildings] = useState([]); const [expandedBuildings, setExpandedBuildings] = useState([]);
const [showFloorModal, setShowFloorModal] = useState(false); const [showFloorModal, setShowFloorModal] = useState(false);
@ -108,12 +114,41 @@ const InfraTable = ({ buildings }) => {
); );
}; };
useEffect(() => { useEffect(() => {
if (buildings && buildings.length > 0) { if (buildings && buildings.length > 0) {
setProjectBuilding(buildings); setProjectBuilding(buildings);
setExpandedBuildings([buildings[0].id]); setExpandedBuildings([buildings[0].id]);
} }
}, [buildings]); }, [buildings]);
const handler = useCallback(
(msg) => {
if (msg.projectIds.some((item) => item == projectId)) {
try {
ProjectRepository.getProjectByprojectId(projectId)
.then((response) => {
cacheData("projectInfo", {
projectId: projectId,
data: response.data,
});
setProjectBuilding(response?.data?.buildings);
signalRHandler?.(response?.data);
showToast(msg.message, "info");
})
.catch((error) => {
console.error(error);
});
} catch (e) {
console.error(e);
}
}
},
[buildings]
);
useEffect(() => {
eventBus.on("infra", handler);
return () => eventBus.off("infra", handler);
}, [handler]);
return ( return (
<div> <div>
{projectBuilding && projectBuilding.length > 0 && ( {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="b74da4c2-d07e-46f2-9919-e75e49b12731">Active</option>
<option value="603e994b-a27f-4e5d-a251-f3d69b0498ba">On Hold</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="ef1c356e-0fe0-42df-a5d3-8daee355492d">Inactive</option>
<option value="33deaef9-9af1-4f2a-b443-681ea0d04f81">Completed</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-dialog modal-dialog-scrollable mx-sm-auto mx-1 modal-lg modal-simple modal-edit-user">
<div className="modal-content"> <div className="modal-content">
<div className="modal-header text-center"> <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> </button>
</div> </div>
<p className="m-0 fw-semibold fs-5">Assign Employee</p> <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 [showModal, setShowModal] = useState(false);
const manageProject = useHasUserPermission(MANAGE_PROJECT); const manageProject = useHasUserPermission(MANAGE_PROJECT);
const [CurrentProject, setCurrentProject] = useState(project_data); const [CurrentProject, setCurrentProject] = useState(project_data);
if (project_data == null) { if (project_data == null) {
return <span>incomplete project information</span>; return <span>incomplete project information</span>;
} }
useEffect(() => {
setCurrentProject(project_data);
}, [project_data]);
const handleShow = () => setShowModal(true); const handleShow = () => setShowModal(true);
const handleClose = () => setShowModal(false); 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 moment from "moment";
import { getDateDifferenceInDays } from "../../utils/dateUtils"; import { getDateDifferenceInDays } from "../../utils/dateUtils";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
@ -22,6 +22,10 @@ const ProjectCard = ({ projectData, recall }) => {
const ManageProject = useHasUserPermission(MANAGE_PROJECT); const ManageProject = useHasUserPermission(MANAGE_PROJECT);
const [modifyProjectLoading, setMdifyProjectLoading] = useState(false); const [modifyProjectLoading, setMdifyProjectLoading] = useState(false);
useEffect(()=>{
setProjectInfo(projectData);
},[projectData])
// console.log("in card view",projectInfo);
const handleShow = async () => { const handleShow = async () => {
try { try {
setMdifyProjectLoading(true); 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 "./ProjectInfra.css";
import BuildingModel from "./Infrastructure/BuildingModel"; import BuildingModel from "./Infrastructure/BuildingModel";
import FloorModel from "./Infrastructure/FloorModel"; import FloorModel from "./Infrastructure/FloorModel";
@ -12,20 +12,20 @@ import ProjectModal from "./ProjectModal";
import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { MANAGE_PROJECT_INFRA } from "../../utils/constants"; import { MANAGE_PROJECT_INFRA } from "../../utils/constants";
import InfraTable from "./Infrastructure/InfraTable"; 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 { useProjectDetails } from "../../hooks/useProjects";
import {useDispatch, useSelector} from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import {refreshData} from "../../slices/localVariablesSlice"; import { refreshData } from "../../slices/localVariablesSlice";
import eventBus from "../../services/eventBus";
const ProjectInfra = ({ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
data, const reloadedData = useSelector((store) => store.localVariables.reload);
onDataChange,
eachSiteEngineer,
} ) =>
{
const reloadedData = useSelector((store)=>store.localVariables.reload)
const [expandedBuildings, setExpandedBuildings] = useState([]); 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 [project, setProject] = useState(projects_Details);
const [modalConfig, setModalConfig] = useState({ type: null, data: null }); const [modalConfig, setModalConfig] = useState({ type: null, data: null });
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
@ -39,8 +39,8 @@ const ProjectInfra = ({
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [clearFormTrigger, setClearFormTrigger] = useState(false); const [clearFormTrigger, setClearFormTrigger] = useState(false);
const [CurrentBuilding, setCurrentBuilding] = useState(""); const [CurrentBuilding, setCurrentBuilding] = useState("");
const [ showModal, setShowModal ] = useState( false ); const [showModal, setShowModal] = useState(false);
const dispatch = useDispatch() const dispatch = useDispatch();
useEffect(() => { useEffect(() => {
setProject(projects_Details); setProject(projects_Details);
@ -143,8 +143,8 @@ const ProjectInfra = ({
existingItem.workItemId === existingItem.workItemId ===
workItem.workItemId workItem.workItemId
) )
? [...workArea.workItems] // Create a new array to trigger re-render ? [...workArea.workItems] // Create a new array to trigger re-render
: [...workArea.workItems, workItem], : [...workArea.workItems, workItem],
} }
: workArea : workArea
), ),
@ -156,15 +156,15 @@ const ProjectInfra = ({
); );
updatedProject.buildings = updatedBuildings; updatedProject.buildings = updatedBuildings;
// workItem update, but having local state issue there for needed to calling api // workItem update, but having local state issue there for needed to calling api
clearCacheKey( "projectInfo" ) clearCacheKey("projectInfo");
refetch() refetch();
cacheData("projectInfo", { cacheData("projectInfo", {
projectId: updatedProject.id, projectId: updatedProject.id,
data: updatedProject, data: updatedProject,
}); });
setProject( updatedProject ); setProject(updatedProject);
// closeTaskModel() // closeTaskModel()
} }
}) })
@ -173,11 +173,8 @@ const ProjectInfra = ({
}); });
}; };
const submitData = async (infraObject) => { const submitData = async (infraObject) => {
try try {
{
let response = await ProjectRepository.manageProjectInfra(infraObject); let response = await ProjectRepository.manageProjectInfra(infraObject);
const entity = response.data; const entity = response.data;
@ -209,9 +206,8 @@ const ProjectInfra = ({
setProject((prevProject) => ({ setProject((prevProject) => ({
...prevProject, ...prevProject,
buildings: updatedBuildings, buildings: updatedBuildings,
} ) ); }));
// closeBuildingModel() // closeBuildingModel()
} }
// Handle the floor data // Handle the floor data
else if (entity.floor) { else if (entity.floor) {
@ -247,12 +243,11 @@ const ProjectInfra = ({
projectId: updatedProject.id, projectId: updatedProject.id,
data: updatedProject, data: updatedProject,
}); });
setProject( updatedProject ); setProject(updatedProject);
// closeFloorModel() // closeFloorModel()
} }
// Handle the work area data // Handle the work area data
else if ( entity.workArea ) else if (entity.workArea) {
{
let buildingId = infraObject[0].workArea.buildingId; let buildingId = infraObject[0].workArea.buildingId;
const { floorId, areaName, id } = entity.workArea; const { floorId, areaName, id } = entity.workArea;
@ -291,7 +286,7 @@ const ProjectInfra = ({
projectId: updatedProject.id, projectId: updatedProject.id,
data: updatedProject, data: updatedProject,
}); });
setProject( updatedProject ); setProject(updatedProject);
// closeWorkAreaModel() // closeWorkAreaModel()
} }
// Handle the task (workItem) data // Handle the task (workItem) data
@ -303,7 +298,6 @@ const ProjectInfra = ({
} }
}; };
const toggleBuilding = (id) => { const toggleBuilding = (id) => {
setExpandedBuildings((prev) => setExpandedBuildings((prev) =>
prev.includes(id) ? prev.filter((bid) => bid !== id) : [...prev, id] prev.includes(id) ? prev.filter((bid) => bid !== id) : [...prev, id]
@ -344,15 +338,17 @@ const ProjectInfra = ({
}; };
const handleShow = () => setShowModal(true); const handleShow = () => setShowModal(true);
const handleClose = () => setShowModal( false ); const handleClose = () => setShowModal(false);
useEffect( () => useEffect(() => {
{ if (reloadedData) {
if (reloadedData) refetch();
{ dispatch(refreshData(false));
refetch() }
dispatch( refreshData( false ) ) }, [reloadedData]);
}
},[reloadedData]) const signalRHandler = (response) => {
setProject(response);
}
return ( return (
<> <>
@ -420,7 +416,6 @@ const ProjectInfra = ({
> >
<TaskModel <TaskModel
project={project} project={project}
onClose={closeTaskModel} onClose={closeTaskModel}
onSubmit={handleTaskModelFormSubmit} onSubmit={handleTaskModelFormSubmit}
clearTrigger={clearFormTrigger} clearTrigger={clearFormTrigger}
@ -482,8 +477,9 @@ const ProjectInfra = ({
{project && project.buildings?.length > 0 && ( {project && project.buildings?.length > 0 && (
<InfraTable <InfraTable
buildings={project?.buildings} buildings={project?.buildings}
project={project} projectId={project.id}
handleFloor={submitData} handleFloor={submitData}
signalRHandler = {signalRHandler}
/> />
)} )}
</div> </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 MapUsers from "./MapUsers";
import { Link, NavLink, useNavigate } from "react-router-dom"; import { Link, NavLink, useNavigate } from "react-router-dom";
@ -13,6 +13,7 @@ import useMaster from "../../hooks/masterHook/useMaster";
import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { ASSIGN_TO_PROJECT } from "../../utils/constants"; import { ASSIGN_TO_PROJECT } from "../../utils/constants";
import ConfirmModal from "../common/ConfirmModal"; import ConfirmModal from "../common/ConfirmModal";
import eventBus from "../../services/eventBus";
const Teams = ({ project }) => { const Teams = ({ project }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -173,6 +174,33 @@ const Teams = ({ project }) => {
} }
const closeDeleteModal = ()=> setIsDeleteModal(false) 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 ( return (
<> <>
<div <div
@ -322,10 +350,10 @@ const Teams = ({ project }) => {
{" "} {" "}
{removingEmployeeId === item.id ? ( {removingEmployeeId === item.id ? (
<div <div
class="spinner-border spinner-border-sm text-primary" className="spinner-border spinner-border-sm text-primary"
role="status" role="status"
> >
<span class="visually-hidden"> <span className="visually-hidden">
Loading... Loading...
</span> </span>
</div> </div>

View File

@ -37,7 +37,7 @@ export const useAttendace =(projectId)=>{
} }
},[projectId]) },[projectId])
return {attendance,loading,error} return {attendance,loading,error,recall:fetchData}
} }
export const useEmployeeAttendacesLog = (id) => { 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 AuthRepository from "../repositories/AuthRepository";
import {cacheProfileData, getCachedProfileData} from "../slices/apiDataManager"; import {cacheData, cacheProfileData, getCachedData, getCachedProfileData} from "../slices/apiDataManager";
import {useSelector} from "react-redux"; import {useSelector} from "react-redux";
import eventBus from "../services/eventBus";
let hasFetched = false; let hasFetched = false;
let hasReceived = false;
export const useProfile = () => { export const useProfile = () => {
const loggedUser = useSelector( ( store ) => store.globalVariables.loginUser ); const loggedUser = useSelector( ( store ) => store.globalVariables.loginUser );
@ -24,7 +26,7 @@ export const useProfile = () => {
} }
}; };
useEffect(() => { const validation = () => {
if (!hasFetched) { if (!hasFetched) {
hasFetched = true; hasFetched = true;
if (!loggedUser) { if (!loggedUser) {
@ -35,8 +37,26 @@ export const useProfile = () => {
} }
setProfile(loggedUser); setProfile(loggedUser);
}
useEffect(() => {
validation();
}, [loggedUser]); }, [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 }; 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 { cacheData, getCachedData } from "../slices/apiDataManager";
import ProjectRepository from "../repositories/ProjectRepository"; import ProjectRepository from "../repositories/ProjectRepository";
import { useProfile } from "./useProfile"; import { useProfile } from "./useProfile";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { setProjectId } from "../slices/localVariablesSlice"; import { setProjectId } from "../slices/localVariablesSlice";
import EmployeeList from "../components/Directory/EmployeeList"; import EmployeeList from "../components/Directory/EmployeeList";
import eventBus from "../services/eventBus";
export const useProjects = () => { export const useProjects = () => {
const loggedUser = useSelector((store) => store.globalVariables.loginUser); const loggedUser = useSelector((store) => store.globalVariables.loginUser);
@ -174,6 +175,7 @@ export const useProjectName = () => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [projectNames, setProjectName] = useState([]); const [projectNames, setProjectName] = useState([]);
const [Error, setError] = useState(); const [Error, setError] = useState();
const dispatch = useDispatch();
const fetchData = async () => { const fetchData = async () => {
try { try {
@ -181,6 +183,9 @@ export const useProjectName = () => {
setProjectName(response.data); setProjectName(response.data);
cacheData("basicProjectNameList", response.data); cacheData("basicProjectNameList", response.data);
setLoading(false); setLoading(false);
if(response.data.length === 1){
dispatch(setProjectId(response.data[0]?.id));
}
} catch (err) { } catch (err) {
setError("Failed to fetch data."); setError("Failed to fetch data.");
setLoading(false); setLoading(false);
@ -190,5 +195,5 @@ export const useProjectName = () => {
fetchData(); 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 = () => export const useAuditStatus = () =>
{ {
const [ status, setStatus ] = useState( [] ); const [ status, setStatus ] = useState( [] );

View File

@ -2,15 +2,26 @@ import React, { useEffect } from "react";
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router-dom";
import Header from "../components/Layout/Header"; import Header from "../components/Layout/Header";
import Sidebar from "../components/Layout/Sidebar"; import Sidebar from "../components/Layout/Sidebar";
import { startSignalR, stopSignalR } from "../services/signalRService";
import Footer from "../components/Layout/Footer"; import Footer from "../components/Layout/Footer";
import FloatingMenu from "../components/common/FloatingMenu"; import FloatingMenu from "../components/common/FloatingMenu";
import { FabProvider } from "../Context/FabContext"; import { FabProvider } from "../Context/FabContext";
import { useSelector } from "react-redux";
const HomeLayout = () => { const HomeLayout = () => {
const loggedUser = useSelector((store) => store.globalVariables.loginUser);
useEffect(() => { useEffect(() => {
Main(); Main();
}, []); }, []);
useEffect(() => {
if (loggedUser) {
startSignalR(loggedUser);
}
return () => {
stopSignalR();
};
}, [loggedUser]);
return ( return (
<FabProvider> <FabProvider>
<div className="layout-wrapper layout-content-navbar"> <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 { import {
cacheData, cacheData,
clearCacheKey,
getCachedData, getCachedData,
getCachedProfileData, getCachedProfileData,
} from "../../slices/apiDataManager"; } from "../../slices/apiDataManager";
@ -18,6 +19,8 @@ import { markCurrentAttendance } from "../../slices/apiSlice/attendanceAllSlice"
import { hasUserPermission } from "../../utils/authUtils"; import { hasUserPermission } from "../../utils/authUtils";
import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { REGULARIZE_ATTENDANCE } from "../../utils/constants"; import { REGULARIZE_ATTENDANCE } from "../../utils/constants";
import eventBus from "../../services/eventBus";
import AttendanceRepository from "../../repositories/AttendanceRepository";
const AttendancePage = () => { const AttendancePage = () => {
const [activeTab, setActiveTab] = useState("all"); const [activeTab, setActiveTab] = useState("all");
@ -25,7 +28,11 @@ const AttendancePage = () => {
const loginUser = getCachedProfileData(); const loginUser = getCachedProfileData();
var selectedProject = useSelector((store) => store.localVariables.projectId); var selectedProject = useSelector((store) => store.localVariables.projectId);
// const { projects, loading: projectLoading } = useProjects(); // const { projects, loading: projectLoading } = useProjects();
const { attendance, loading: attLoading } = useAttendace(selectedProject); const {
attendance,
loading: attLoading,
recall: attrecall,
} = useAttendace(selectedProject);
const [attendances, setAttendances] = useState(); const [attendances, setAttendances] = useState();
const [empRoles, setEmpRoles] = useState(null); const [empRoles, setEmpRoles] = useState(null);
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
@ -39,6 +46,40 @@ const AttendancePage = () => {
date: new Date().toLocaleDateString(), 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) => { const getRole = (roleId) => {
if (!empRoles) return "Unassigned"; if (!empRoles) return "Unassigned";
if (!roleId) return "Unassigned"; if (!roleId) return "Unassigned";
@ -112,9 +153,20 @@ const AttendancePage = () => {
// ) // )
// : attendances; // : attendances;
const filteredAttendance = ShowPending const filteredAttendance = ShowPending
? attendances?.filter((att) => att?.checkInTime !== null && att?.checkOutTime === null) ? attendances?.filter(
(att) => att?.checkInTime !== null && att?.checkOutTime === null
)
: attendances; : attendances;
useEffect(() => {
eventBus.on("attendance", handler);
return () => eventBus.off("attendance", handler);
}, [handler]);
useEffect(() => {
eventBus.on("employee", employeeHandler);
return () => eventBus.off("employee", employeeHandler);
}, [employeeHandler]);
return ( return (
<> <>
{isCreateModalOpen && modelConfig && ( {isCreateModalOpen && modelConfig && (
@ -213,15 +265,10 @@ const AttendancePage = () => {
Regularization Regularization
</button> </button>
</li> </li>
</ul> </ul>
<div className="tab-content attedanceTabs py-0 px-1 px-sm-3"> <div className="tab-content attedanceTabs py-0 px-1 px-sm-3">
{activeTab === "all" && ( {activeTab === "all" && (
<> <>
<div className="tab-pane fade show active py-0"> <div className="tab-pane fade show active py-0">
<Attendance <Attendance
attendance={filteredAttendance} attendance={filteredAttendance}
@ -231,8 +278,13 @@ const AttendancePage = () => {
showOnlyCheckout={ShowPending} showOnlyCheckout={ShowPending}
/> />
</div> </div>
{!attLoading && filteredAttendance?.length === 0 && ( {!attLoading && filteredAttendance?.length === 0 && (
<p> {ShowPending ? "No Pending Available" : "No Employee assigned yet."} </p> <p>
{" "}
{ShowPending
? "No Pending Available"
: "No Employee assigned yet."}{" "}
</p>
)} )}
</> </>
)} )}
@ -254,7 +306,7 @@ const AttendancePage = () => {
</div> </div>
)} )}
{attLoading && <span>Loading..</span>} {attLoading && <span>Loading..</span>}
{!attLoading && !attendances && <span>Not Found</span>} {!attLoading && !attendances && <span>Not Found</span>}
</div> </div>
</div> </div>

View File

@ -57,7 +57,7 @@ const DailyTask = () => {
const { const {
TaskList, TaskList,
loading: task_loading, loading: task_loading,
error: task_error, error: task_error,
refetch, refetch,
} = useTaskList( } = useTaskList(
@ -66,7 +66,7 @@ const DailyTask = () => {
initialized ? dateRange.endDate : null initialized ? dateRange.endDate : null
); );
const [TaskLists, setTaskLists] = useState([]); const [TaskLists, setTaskLists] = useState([]);
const [dates, setDates] = useState([]); const [dates, setDates] = useState([]);
const popoverRefs = useRef([]); const popoverRefs = useRef([]);
@ -116,7 +116,7 @@ const DailyTask = () => {
}, [TaskLists]); }, [TaskLists]);
const [selectedTask, selectTask] = useState(null); 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 [isModalOpen, setIsModalOpen] = useState(false);
const [isModalOpenComment, setIsModalOpenComment] = useState(false); const [isModalOpenComment, setIsModalOpenComment] = useState(false);
@ -126,8 +126,8 @@ const DailyTask = () => {
const openComment = () => setIsModalOpenComment(true); const openComment = () => setIsModalOpenComment(true);
const closeCommentModal = () => setIsModalOpenComment(false); const closeCommentModal = () => setIsModalOpenComment(false);
const [ IsSubTaskNeeded, setIsSubTaskNeeded ] = useState( false ) const [IsSubTaskNeeded, setIsSubTaskNeeded] = useState(false);
const[SubTaskData,setSubTaskData] = useState() const [SubTaskData, setSubTaskData] = useState();
const handletask = (task) => { const handletask = (task) => {
selectTask(task); selectTask(task);
openModal(); openModal();
@ -140,16 +140,16 @@ const DailyTask = () => {
trigger: "focus", trigger: "focus",
placement: "left", placement: "left",
html: true, html: true,
content: el.getAttribute("data-bs-content"), content: el.getAttribute("data-bs-content"),
}); });
} }
}); });
},[dates, TaskLists]); }, [dates, TaskLists]);
const handleProjectChange = (e) => { const handleProjectChange = (e) => {
const newProjectId = e.target.value; const newProjectId = e.target.value;
dispatch(setProjectId(newProjectId)); dispatch(setProjectId(newProjectId));
setTaskLists([]); setTaskLists([]);
setFilters({ setFilters({
selectedBuilding: "", selectedBuilding: "",
selectedFloors: [], selectedFloors: [],
@ -157,24 +157,19 @@ const DailyTask = () => {
}); });
}; };
const handleCloseAction = (IsSubTask) => const handleCloseAction = (IsSubTask) => {
{ if (IsSubTask) {
if ( IsSubTask ) setIsSubTaskNeeded(true);
{ setIsModalOpenComment(false);
setIsSubTaskNeeded( true ) } else {
setIsModalOpenComment(false) refetch(selectedProject, dateRange.startDate, dateRange.endDate);
setIsModalOpenComment(false);
} else
{
refetch( selectedProject, dateRange.startDate, dateRange.endDate );
setIsModalOpenComment(false)
} }
} };
const hanleCloseSubTask = () => const hanleCloseSubTask = () => {
{ setIsSubTaskNeeded(false);
setIsSubTaskNeeded( false ) setComment(null);
setComment(null) };
}
return ( return (
<> <>
<div <div
@ -189,23 +184,30 @@ const DailyTask = () => {
closeModal={closeModal} closeModal={closeModal}
refetch={refetch} refetch={refetch}
/> />
</div> </div>
{isModalOpenComment && ( {isModalOpenComment && (
<GlobalModel isOpen={isModalOpenComment} size="lg" closeModal={()=>setIsModalOpenComment(false)}> <GlobalModel
<ReportTaskComments isOpen={isModalOpenComment}
size="lg"
closeModal={() => setIsModalOpenComment(false)}
>
<ReportTaskComments
commentsData={comments.task} commentsData={comments.task}
actionAllow={comments.isActionAllow} actionAllow={comments.isActionAllow}
handleCloseAction={handleCloseAction} handleCloseAction={handleCloseAction}
closeModal={closeCommentModal} closeModal={closeCommentModal}
/> />
</GlobalModel> </GlobalModel>
)} )}
{IsSubTaskNeeded && ( {IsSubTaskNeeded && (
<GlobalModel isOpen={IsSubTaskNeeded} size="lg" closeModal={hanleCloseSubTask}> <GlobalModel
<SubTask assignData={comments.task} onClose={hanleCloseSubTask} /> isOpen={IsSubTaskNeeded}
size="lg"
closeModal={hanleCloseSubTask}
>
<SubTask activity={comments.task} onClose={hanleCloseSubTask} />
</GlobalModel> </GlobalModel>
)} )}
@ -227,7 +229,7 @@ const DailyTask = () => {
dateFormat="DD-MM-YYYY" dateFormat="DD-MM-YYYY"
/> />
<FilterIcon <FilterIcon
taskListData={TaskList} taskListData={TaskList}
onApplyFilters={setFilters} onApplyFilters={setFilters}
currentSelectedBuilding={filters.selectedBuilding} currentSelectedBuilding={filters.selectedBuilding}
currentSelectedFloors={filters.selectedFloors} currentSelectedFloors={filters.selectedFloors}
@ -414,7 +416,7 @@ const DailyTask = () => {
</div> </div>
</td> </td>
<td className="text-center"> <td className="text-center">
<div className="d-flex justify-content-center"> <div className="d-flex justify-content-end">
<button <button
type="button" type="button"
className={`btn btn-xs btn-primary ${ className={`btn btn-xs btn-primary ${
@ -429,25 +431,34 @@ const DailyTask = () => {
> >
Report Report
</button> </button>
<button {task.reportedDate && (
type="button" <button
className={`btn btn-xs btn-warning ${ type="button"
task.reportedDate == null className={`btn btn-xs btn-warning ${
? "d-none" task.reportedDate && task.approvedBy
: "" ? "d-none"
}`} : ""
onClick={() => { }`}
setComment({task:task,isActionAllow:true}); onClick={() => {
openComment(); setComment({
}} task: task,
> isActionAllow: true,
Take Action });
</button> openComment();
}}
>
QC
</button>
)}
<button <button
type="button" type="button"
className="btn btn-xs btn-primary ms-2" className="btn btn-xs btn-primary ms-2"
onClick={() => { onClick={() => {
setComment({task:task,isActionAllow:false}); setComment({
task: task,
isActionAllow: false,
});
openComment(); 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 moment from "moment";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
import { Link, NavLink, useNavigate } from "react-router-dom"; import { Link, NavLink, useNavigate } from "react-router-dom";
@ -23,6 +23,8 @@ import EmployeeRepository from "../../repositories/EmployeeRepository";
import ManageEmployee from "../../components/Employee/ManageEmployee"; import ManageEmployee from "../../components/Employee/ManageEmployee";
import ConfirmModal from "../../components/common/ConfirmModal"; import ConfirmModal from "../../components/common/ConfirmModal";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import eventBus from "../../services/eventBus";
import { newlineChars } from "pdf-lib";
const EmployeeList = () => { const EmployeeList = () => {
const selectedProjectId = useSelector((store) => store.localVariables.projectId); const selectedProjectId = useSelector((store) => store.localVariables.projectId);
@ -202,7 +204,6 @@ const EmployeeList = () => {
}; };
const handleOpenDelete = (employee) => { const handleOpenDelete = (employee) => {
console.log(employee);
setSelectedEmpFordelete(employee); setSelectedEmpFordelete(employee);
setIsDeleteModalOpen(true); setIsDeleteModalOpen(true);
}; };
@ -218,6 +219,20 @@ const EmployeeList = () => {
setSelectedProject(selectedProjectId || ""); setSelectedProject(selectedProjectId || "");
}, [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 ( return (
<> <>

View File

@ -1,5 +1,5 @@
import { useParams } from "react-router-dom"; 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 ActivityTimeline from "../../components/Project/ActivityTimeline";
import ProjectOverview from "../../components/Project/ProjectOverview"; import ProjectOverview from "../../components/Project/ProjectOverview";
@ -11,7 +11,7 @@ import ProjectInfra from "../../components/Project/ProjectInfra";
import Loader from "../../components/common/Loader"; import Loader from "../../components/common/Loader";
import WorkPlan from "../../components/Project/WorkPlan"; import WorkPlan from "../../components/Project/WorkPlan";
import Breadcrumb from "../../components/common/Breadcrumb"; 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 ProjectRepository from "../../repositories/ProjectRepository";
import { ActivityeRepository } from "../../repositories/MastersRepository"; import { ActivityeRepository } from "../../repositories/MastersRepository";
import "./ProjectDetails.css"; import "./ProjectDetails.css";
@ -23,6 +23,7 @@ import { useDispatch } from "react-redux";
import { setProjectId } from "../../slices/localVariablesSlice"; import { setProjectId } from "../../slices/localVariablesSlice";
import { ComingSoonPage } from "../Misc/ComingSoonPage"; import { ComingSoonPage } from "../Misc/ComingSoonPage";
import Directory from "../Directory/Directory"; import Directory from "../Directory/Directory";
import eventBus from "../../services/eventBus";
const ProjectDetails = () => { const ProjectDetails = () => {
let { projectId } = useParams(); let { projectId } = useParams();
@ -121,7 +122,7 @@ const ProjectDetails = () => {
case "directory": { case "directory": {
return ( return (
<div className="row"> <div className="row">
<Directory IsPage={ false} prefernceContacts={projectDetails.id} /> <Directory IsPage={false} prefernceContacts={projectDetails.id} />
</div> </div>
); );
} }
@ -137,6 +138,31 @@ const ProjectDetails = () => {
setProjectDetails(projects_Details); setProjectDetails(projects_Details);
}, [projects_Details, projectId]); }, [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 ( 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 ProjectCard from "../../components/Project/ProjectCard";
import ManageProjectInfo from "../../components/Project/ManageProjectInfo"; import ManageProjectInfo from "../../components/Project/ManageProjectInfo";
import Breadcrumb from "../../components/common/Breadcrumb"; import Breadcrumb from "../../components/common/Breadcrumb";
@ -6,11 +6,14 @@ import ProjectRepository from "../../repositories/ProjectRepository";
import { useProjects } from "../../hooks/useProjects"; import { useProjects } from "../../hooks/useProjects";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
import { getCachedData, cacheData } from "../../slices/apiDataManager"; import { getCachedData, cacheData, clearCacheKey } from "../../slices/apiDataManager";
import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { useProfile } from "../../hooks/useProfile"; import { useProfile } from "../../hooks/useProfile";
import { ITEMS_PER_PAGE, MANAGE_PROJECT } from "../../utils/constants"; import { ITEMS_PER_PAGE, MANAGE_PROJECT } from "../../utils/constants";
import ProjectListView from "./ProjectListView"; import ProjectListView from "./ProjectListView";
import eventBus from "../../services/eventBus";
import { clearApiCacheKey } from "../../slices/apiCacheSlice";
import { defaultCheckBoxAppearanceProvider } from "pdf-lib";
const ProjectList = () => { const ProjectList = () => {
const { profile: loginUser } = useProfile(); const { profile: loginUser } = useProfile();
@ -37,7 +40,7 @@ const ProjectList = () => {
const handleShow = () => setShowModal(true); const handleShow = () => setShowModal(true);
const handleClose = () => setShowModal(false); const handleClose = () => setShowModal(false);
const sortingProject = (projects) =>{ const sortingProject = (projects) => {
if (!loading && Array.isArray(projects)) { if (!loading && Array.isArray(projects)) {
const grouped = {}; const grouped = {};
projects.forEach((project) => { projects.forEach((project) => {
@ -53,13 +56,12 @@ const ProjectList = () => {
a.name.toLowerCase()?.localeCompare(b.name.toLowerCase()) a.name.toLowerCase()?.localeCompare(b.name.toLowerCase())
) )
); );
setProjectList(sortedGrouped); setProjectList(sortedGrouped);
} }
} };
useEffect(() => { useEffect(() => {
sortingProject(projects) sortingProject(projects);
}, [projects, loginUser?.projects, loading]); }, [projects, loginUser?.projects, loading]);
useEffect(() => { useEffect(() => {
@ -70,16 +72,16 @@ const ProjectList = () => {
} }
}, [loginUser, HasManageProjectPermission]); }, [loginUser, HasManageProjectPermission]);
const handleSubmitForm = (newProject,setloading,reset) => { const handleSubmitForm = (newProject, setloading, reset) => {
ProjectRepository.manageProject(newProject) ProjectRepository.manageProject(newProject)
.then((response) => { .then((response) => {
const cachedProjects = getCachedData("projectslist") || []; const cachedProjects = getCachedData("projectslist") || [];
const updatedProjects = [...cachedProjects, response.data]; const updatedProjects = [...cachedProjects, response.data];
cacheData("projectslist", updatedProjects); cacheData("projectslist", updatedProjects);
setProjectList( ( prev ) => [ ...prev, response.data ] ); setProjectList((prev) => [...prev, response.data]);
setloading( false ) setloading(false);
reset() reset();
sortingProject(getCachedData("projectslist")) sortingProject(getCachedData("projectslist"));
showToast("Project Created successfully.", "success"); showToast("Project Created successfully.", "success");
setShowModal(false); setShowModal(false);
}) })
@ -123,7 +125,6 @@ const ProjectList = () => {
indexOfLastItem indexOfLastItem
); );
const totalPages = Math.ceil(filteredProjects.length / itemsPerPage); const totalPages = Math.ceil(filteredProjects.length / itemsPerPage);
useEffect(() => { useEffect(() => {
const tooltipTriggerList = Array.from( const tooltipTriggerList = Array.from(
document.querySelectorAll('[data-bs-toggle="tooltip"]') document.querySelectorAll('[data-bs-toggle="tooltip"]')
@ -131,6 +132,52 @@ const ProjectList = () => {
tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el)); 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 ( return (
<> <>
<div <div
@ -200,49 +247,48 @@ const ProjectList = () => {
<i className="bx bx-list-ul bx-sm"></i> <i className="bx bx-list-ul bx-sm"></i>
</button> </button>
</div> </div>
<div className="dropdown ms-3"> <div className="dropdown ms-3">
<a <a
className="dropdown-toggle hide-arrow cursor-pointer" className="dropdown-toggle hide-arrow cursor-pointer"
data-bs-toggle="dropdown" data-bs-toggle="dropdown"
aria-expanded="false" aria-expanded="false"
> >
<i className="bx bx-filter bx-lg"></i> <i className="bx bx-filter bx-lg"></i>
</a> </a>
<ul className="dropdown-menu p-2 text-capitalize"> <ul className="dropdown-menu p-2 text-capitalize">
{[ {[
{ {
id: "b74da4c2-d07e-46f2-9919-e75e49b12731", id: "b74da4c2-d07e-46f2-9919-e75e49b12731",
label: "Active", label: "Active",
}, },
{ {
id: "603e994b-a27f-4e5d-a251-f3d69b0498ba", id: "603e994b-a27f-4e5d-a251-f3d69b0498ba",
label: "On Hold", label: "On Hold",
}, },
{ {
id: "ef1c356e-0fe0-42df-a5d3-8daee355492d", id: "ef1c356e-0fe0-42df-a5d3-8daee355492d",
label: "Inactive", label: "Inactive",
}, },
{ {
id: "33deaef9-9af1-4f2a-b443-681ea0d04f81", id: "33deaef9-9af1-4f2a-b443-681ea0d04f81",
label: "Completed", label: "Completed",
}, },
].map(({ id, label }) => ( ].map(({ id, label }) => (
<li key={id}> <li key={id}>
<div className="form-check"> <div className="form-check">
<input <input
className="form-check-input " className="form-check-input "
type="checkbox" type="checkbox"
checked={selectedStatuses.includes(id)} checked={selectedStatuses.includes(id)}
onChange={() => handleStatusChange(id)} onChange={() => handleStatusChange(id)}
/> />
<label className="form-check-label">{label}</label> <label className="form-check-label">{label}</label>
</div> </div>
</li> </li>
))} ))}
</ul> </ul>
</div> </div>
</div> </div>
<div> <div>
@ -341,7 +387,11 @@ const ProjectList = () => {
</tr> </tr>
) : ( ) : (
currentItems.map((project) => ( currentItems.map((project) => (
<ProjectListView key={project.id} projectData={project} recall={sortingProject} /> <ProjectListView
key={project.id}
projectData={project}
recall={sortingProject}
/>
)) ))
)} )}
</tbody> </tbody>
@ -349,7 +399,11 @@ const ProjectList = () => {
</div> </div>
) : ( ) : (
currentItems.map((project) => ( currentItems.map((project) => (
<ProjectCard key={project.id} projectData={project} recall={sortingProject} /> <ProjectCard
key={project.id}
projectData={project}
recall={sortingProject}
/>
)) ))
)} )}
</div> </div>

View File

@ -21,6 +21,9 @@ const ProjectListView = ({ projectData, recall }) => {
const [showModal, setShowModal] = useState(false); const [showModal, setShowModal] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
const ManageProject = useHasUserPermission(MANAGE_PROJECT); const ManageProject = useHasUserPermission(MANAGE_PROJECT);
useEffect(()=>{
setProjectInfo(projectData);
},[projectData])
const handleShow = async () => { const handleShow = async () => {
try { try {

View File

@ -14,12 +14,12 @@ export const TasksRepository = {
return api.get(url); return api.get(url);
}, },
getTaskById:(id)=>api.get(`/api/task/get/${id}`),
reportTask: (data) => api.post("api/task/report", data), reportTask: (data) => api.post("api/task/report", data),
taskComments: ( data ) => api.post( "api/task/comment", data ), taskComments: ( data ) => api.post( "api/task/comment", data ),
auditTask: ( data ) => api.post( '/api/task/approve', data ),
assignTask:(data) =>api.post('/api/task/assign',data)
auditTask: ( data ) => api.post( '/api/task/approve', 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 { useNavigate } from "react-router-dom";
import axiosRetry from "axios-retry"; import axiosRetry from "axios-retry";
import showToast from "../services/toastService"; import showToast from "../services/toastService";
import { startSignalR, stopSignalR } from "../services/signalRService";
const base_Url = process.env.VITE_BASE_URL; const base_Url = process.env.VITE_BASE_URL;
// const base_Url = "https://api.marcoaiot.com"; // const base_Url = "https://api.marcoaiot.com";
export const axiosClient = axios.create({ export const axiosClient = axios.create({
@ -69,6 +70,8 @@ axiosClient.interceptors.response.use(
return Promise.reject(error); return Promise.reject(error);
} }
stopSignalR();
try { try {
// Refresh token // Refresh token
const res = await axiosClient.post("/api/Auth/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("jwtToken", token);
localStorage.setItem("refreshToken", newRefreshToken); localStorage.setItem("refreshToken", newRefreshToken);
startSignalR()
// Set Authorization header // Set Authorization header
originalRequest.headers["Authorization"] = `Bearer ${token}`; originalRequest.headers["Authorization"] = `Bearer ${token}`;

View File

@ -4,8 +4,8 @@ export const getProjectStatusName = (statusId) => {
return "Active"; return "Active";
case "603e994b-a27f-4e5d-a251-f3d69b0498ba": case "603e994b-a27f-4e5d-a251-f3d69b0498ba":
return "On Hold"; return "On Hold";
// case 3: case "cdad86aa-8a56-4ff4-b633-9c629057dfef":
// return "Suspended"; return "In Progress";
case "ef1c356e-0fe0-42df-a5d3-8daee355492d": case "ef1c356e-0fe0-42df-a5d3-8daee355492d":
return "Inactive"; return "Inactive";
case "33deaef9-9af1-4f2a-b443-681ea0d04f81": case "33deaef9-9af1-4f2a-b443-681ea0d04f81":
@ -23,8 +23,8 @@ export const getProjectStatusColor = (statusId) => {
return "bg-label-info"; return "bg-label-info";
case "33deaef9-9af1-4f2a-b443-681ea0d04f81": case "33deaef9-9af1-4f2a-b443-681ea0d04f81":
return "bg-label-secondary"; return "bg-label-secondary";
case 5: case "cdad86aa-8a56-4ff4-b633-9c629057dfef":
return "bg-label-dark"; return "bg-label-success";
} }
}; };