Merge branch 'main' of https://git.marcoaiot.com/admin/marco.pms.web into pramod_Task#505

This commit is contained in:
Pramod Mahajan 2025-06-19 16:48:47 +05:30
commit 52a578972e
31 changed files with 942 additions and 179 deletions

195
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "0.0.0",
"dependencies": {
"@hookform/resolvers": "^3.10.0",
"@microsoft/signalr": "^8.0.7",
"@reduxjs/toolkit": "^2.5.0",
"@types/web": "^0.0.216",
"@vitejs/plugin-react": "^4.3.4",
@ -17,6 +18,7 @@
"axios-retry": "^4.5.0",
"dotenv": "^16.4.7",
"dotenv-webpack": "^8.1.0",
"eventemitter3": "^5.0.1",
"jwt-decode": "^4.0.0",
"localforage": "^1.10.0",
"match-sorter": "^6.3.1",
@ -827,6 +829,19 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@microsoft/signalr": {
"version": "8.0.7",
"resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-8.0.7.tgz",
"integrity": "sha512-PHcdMv8v5hJlBkRHAuKG5trGViQEkPYee36LnJQx4xHOQ5LL4X0nEWIxOp5cCtZ7tu+30quz5V3k0b1YNuc6lw==",
"license": "MIT",
"dependencies": {
"abort-controller": "^3.0.0",
"eventsource": "^2.0.2",
"fetch-cookie": "^2.0.3",
"node-fetch": "^2.6.7",
"ws": "^7.4.5"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -1750,6 +1765,18 @@
"integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==",
"license": "MIT"
},
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"license": "MIT",
"dependencies": {
"event-target-shim": "^5.0.0"
},
"engines": {
"node": ">=6.5"
}
},
"node_modules/acorn": {
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
@ -2980,10 +3007,19 @@
"node": ">=0.10.0"
}
},
"node_modules/event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/eventemitter3": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
"integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==",
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
"license": "MIT"
},
"node_modules/events": {
@ -2996,6 +3032,15 @@
"node": ">=0.8.x"
}
},
"node_modules/eventsource": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz",
"integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==",
"license": "MIT",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@ -3051,6 +3096,16 @@
"reusify": "^1.0.4"
}
},
"node_modules/fetch-cookie": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-2.2.0.tgz",
"integrity": "sha512-h9AgfjURuCgA2+2ISl8GbavpUdR+WGAM2McW/ovn4tVccegp8ZqCKWSBR8uRdM8dDNlx5WdKRWxBYUwteLDCNQ==",
"license": "Unlicense",
"dependencies": {
"set-cookie-parser": "^2.4.8",
"tough-cookie": "^4.0.0"
}
},
"node_modules/file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@ -4202,6 +4257,26 @@
"license": "MIT",
"peer": true
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-releases": {
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
@ -4521,15 +4596,32 @@
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/psl": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
"integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
"license": "MIT",
"dependencies": {
"punycode": "^2.3.1"
},
"funding": {
"url": "https://github.com/sponsors/lupomontero"
}
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
"license": "MIT"
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@ -4578,6 +4670,12 @@
"node": ">=0.10"
}
},
"node_modules/quill/node_modules/eventemitter3": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
"integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==",
"license": "MIT"
},
"node_modules/randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@ -4804,6 +4902,12 @@
"node": ">=0.10.0"
}
},
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
"license": "MIT"
},
"node_modules/reselect": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
@ -5066,6 +5170,12 @@
"randombytes": "^2.1.0"
}
},
"node_modules/set-cookie-parser": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
"license": "MIT"
},
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@ -5441,6 +5551,27 @@
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true
},
"node_modules/tough-cookie": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
"integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
"license": "BSD-3-Clause",
"dependencies": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
"universalify": "^0.2.0",
"url-parse": "^1.5.3"
},
"engines": {
"node": ">=6"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
@ -5567,6 +5698,15 @@
"license": "MIT",
"peer": true
},
"node_modules/universalify": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
"license": "MIT",
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/update-browserslist-db": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
@ -5605,6 +5745,16 @@
"punycode": "^2.1.0"
}
},
"node_modules/url-parse": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
"license": "MIT",
"dependencies": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
}
},
"node_modules/use-sync-external-store": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
@ -5685,6 +5835,12 @@
"node": ">=10.13.0"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/webpack": {
"version": "5.98.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz",
@ -5766,6 +5922,16 @@
"node": ">=4.0"
}
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@ -5903,6 +6069,27 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
"node_modules/ws": {
"version": "7.5.10",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
"license": "MIT",
"engines": {
"node": ">=8.3.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xlsx": {
"version": "0.18.5",
"resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",

View File

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

View File

@ -6,7 +6,9 @@ import RenderAttendanceStatus from "./RenderAttendanceStatus";
import { useSelector, useDispatch } from "react-redux";
import { fetchAttendanceData } from "../../slices/apiSlice/attedanceLogsSlice";
import DateRangePicker from "../common/DateRangePicker";
import { getCachedData } from "../../slices/apiDataManager";
import { clearCacheKey, getCachedData } from "../../slices/apiDataManager";
import eventBus from "../../services/eventBus";
import AttendanceRepository from "../../repositories/AttendanceRepository";
const usePagination = (data, itemsPerPage) => {
const [currentPage, setCurrentPage] = useState(1);
@ -79,7 +81,7 @@ const AttendanceLog = ({
setIsRefreshing(false);
}, [dateRange, projectId, dispatch, isRefreshing]);
useEffect(() => {
const filtering = (data) => {
const filteredData = showOnlyCheckout
? data.filter((item) => item.checkOutTime === null)
: data;
@ -130,6 +132,10 @@ const AttendanceLog = ({
// Create the final sorted array
const finalData = sortedDates.flatMap((date) => groupedByDate[date]);
setProcessedData(finalData);
}
useEffect(() => {
filtering(data)
}, [data, showOnlyCheckout]);
const {
@ -145,6 +151,54 @@ const AttendanceLog = ({
resetPage();
}, [processedData, resetPage]);
const handler = useCallback(
(msg) => {
const { startDate, endDate } = dateRange;
const checkIn = msg.response.checkInTime.substring(0, 10);
if (
projectId === msg.projectId &&
startDate <= checkIn &&
checkIn <= endDate
) {
const updatedAttendance = data.map((item) =>
item.id === msg.response.id
? { ...item, ...msg.response }
: item
);
filtering(updatedAttendance);
resetPage();
}
},
[projectId, dateRange, data, filtering, resetPage]
);
useEffect(() => {
eventBus.on("attendance_log", handler);
return () => eventBus.off("attendance_log", handler);
}, [handler]);
const employeeHandler = useCallback(
(msg) => {
const { startDate, endDate } = dateRange;
if (data.some((item) => item.employeeId == msg.employeeId)) {
dispatch(
fetchAttendanceData({
projectId,
fromDate: startDate,
toDate: endDate,
})
)
}
},
[projectId, dateRange,data]
);
useEffect(() => {
eventBus.on("employee", employeeHandler);
return () => eventBus.off("employee", employeeHandler);
}, [employeeHandler]);
return (
<>
<div

View File

@ -19,7 +19,7 @@ import InfraTable from "../Project/Infrastructure/InfraTable";
const InfraPlanning = () =>
{
const {profile: LoggedUser} = useProfile()
const {profile: LoggedUser, refetch : fetchData} = useProfile()
const dispatch = useDispatch()
const {projects,loading:project_listLoader,error:projects_error} = useProjects()
@ -72,7 +72,7 @@ const InfraPlanning = () =>
{(!project_deatilsLoader && projects_Details?.buildings?.length > 0) && (<InfraTable buildings={projects_Details?.buildings}/>)}
{(!project_deatilsLoader && projects_Details?.buildings?.length > 0) && (<InfraTable buildings={projects_Details?.buildings} projectId={projects_Details.id}/>)}
</div>
</div>
</div>

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { useCallback, useEffect, useState } from "react";
import Avatar from "../common/Avatar";
import { convertShortTime } from "../../utils/dateUtils";
import RegularizationActions from "./RegularizationActions";
@ -6,6 +6,8 @@ import { useSelector } from "react-redux";
import { useRegularizationRequests } from "../../hooks/useAttendance";
import moment from "moment";
import usePagination from "../../hooks/usePagination";
import eventBus from "../../services/eventBus";
import { cacheData, clearCacheKey } from "../../slices/apiDataManager";
const Regularization = ({ handleRequest }) => {
var selectedProject = useSelector((store) => store.localVariables.projectId);
@ -22,16 +24,52 @@ const Regularization = ({ handleRequest }) => {
const nameB = b.firstName.toLowerCase() + b.lastName.toLowerCase();
return nameA?.localeCompare(nameB);
};
const filteredData = [...regularizesList]?.sort(sortByName);
const handler = useCallback(
(msg) => {
if (selectedProject == msg.projectId) {
const updatedAttendance = regularizes?.filter( item => item.id !== msg.response.id );
cacheData("regularizedList", {
data: updatedAttendance,
projectId: selectedProject,
});
// clearCacheKey("regularizedList")
refetch();
}
},
[selectedProject, regularizes]
);
const filteredData = [...regularizesList]?.sort(sortByName);
const { currentPage, totalPages, currentItems, paginate } = usePagination(
filteredData,
10
);
useEffect(() => {
eventBus.on("regularization", handler);
return () => eventBus.off("regularization", handler);
}, [handler]);
const employeeHandler = useCallback(
(msg) => {
if (regularizes.some((item) => item.employeeId == msg.employeeId)) {
refetch();
}
},
[regularizes]
);
useEffect(() => {
eventBus.on("employee", employeeHandler);
return () => eventBus.off("employee", employeeHandler);
}, [employeeHandler]);
return (
<div className="table-responsive text-nowrap" style={{minHeight:"300px"}}>
<div
className="table-responsive text-nowrap"
style={{ minHeight: "300px" }}
>
<table className="table mb-0">
<thead>
<tr>
@ -47,7 +85,11 @@ const Regularization = ({ handleRequest }) => {
</tr>
</thead>
<tbody>
{loading && <td colSpan={6} className="text-center py-5">Loading...</td>}
{loading && (
<td colSpan={6} className="text-center py-5">
Loading...
</td>
)}
{!loading &&
(regularizes?.length > 0 ? (
@ -55,7 +97,7 @@ const Regularization = ({ handleRequest }) => {
<tr key={index}>
<td colSpan={2}>
<div className="d-flex justify-content-start align-items-center">
<Avatar
<Avatar
firstName={att.firstName}
lastName={att.lastName}
></Avatar>
@ -88,17 +130,22 @@ const Regularization = ({ handleRequest }) => {
))
) : (
<tr>
<td colSpan={6}
className="text-center" style={{
<td
colSpan={6}
className="text-center"
style={{
height: "200px",
verticalAlign: "middle",
borderBottom: "none",
}}>No Record Found</td>
}}
>
No Record Found
</td>
</tr>
))}
</tbody>
</table>
{!loading >10 && (
{!loading > 10 && (
<nav aria-label="Page ">
<ul className="pagination pagination-sm justify-content-end py-1">
<li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}>
@ -112,8 +159,9 @@ const Regularization = ({ handleRequest }) => {
{[...Array(totalPages)].map((_, index) => (
<li
key={index}
className={`page-item ${currentPage === index + 1 ? "active" : ""
}`}
className={`page-item ${
currentPage === index + 1 ? "active" : ""
}`}
>
<button
className="page-link "
@ -124,8 +172,9 @@ const Regularization = ({ handleRequest }) => {
</li>
))}
<li
className={`page-item ${currentPage === totalPages ? "disabled" : ""
}`}
className={`page-item ${
currentPage === totalPages ? "disabled" : ""
}`}
>
<button
className="page-link "

View File

@ -1,8 +1,33 @@
import React from "react";
import React, { useCallback, useEffect, useState } from "react";
import { useDashboardProjectsCardData } from "../../hooks/useDashboard_Data";
import eventBus from "../../services/eventBus";
import GlobalRepository from "../../repositories/GlobalRepository";
const Projects = () => {
const { projectsCardData } = useDashboardProjectsCardData();
const [projectData, setProjectsData] = useState(projectsCardData);
useEffect(() => {
setProjectsData(projectsCardData);
}, [projectsCardData]);
const handler = useCallback(
async (msg) => {
try {
const response =
await GlobalRepository.getDashboardProjectsCardData();
setProjectsData(response.data);
} catch (err) {
console.error(err);
}
},
[GlobalRepository]
);
useEffect(() => {
eventBus.on("project", handler);
return () => eventBus.off("project", handler);
}, [handler]);
return (
<div className="card p-3 h-100 text-center d-flex justify-content-between">
@ -15,13 +40,13 @@ const Projects = () => {
<div className="d-flex justify-content-around align-items-start mt-n2">
<div>
<h4 className="mb-0 fw-bold">
{projectsCardData.totalProjects?.toLocaleString()}
{projectData.totalProjects?.toLocaleString()}
</h4>
<small className="text-muted">Total</small>
</div>
<div>
<h4 className="mb-0 fw-bold">
{projectsCardData.ongoingProjects?.toLocaleString()}
{projectData.ongoingProjects?.toLocaleString()}
</h4>
<small className="text-muted">Ongoing</small>
</div>
@ -30,4 +55,4 @@ const Projects = () => {
);
};
export default Projects;
export default Projects;

View File

@ -1,9 +1,29 @@
import React from "react";
import React, { useCallback, useEffect, useState } from "react";
import { useDashboardTeamsCardData } from "../../hooks/useDashboard_Data";
import eventBus from "../../services/eventBus";
const Teams = () => {
const { teamsCardData } = useDashboardTeamsCardData();
const[totalEmployees,setTotalEmployee] = useState(0);
const[inToday,setInToday] = useState(0);
useEffect(() =>{
setTotalEmployee(teamsCardData.totalEmployees)
setInToday(teamsCardData.inToday)
},[teamsCardData.totalEmployees,teamsCardData.inToday])
const handler = useCallback(
(msg) => {
if (msg.activity == 1) {
setInToday(prev => prev + 1);
}
},
[inToday]
);
useEffect(() => {
eventBus.on("attendance", handler);
return () => eventBus.off("attendance", handler);
}, [handler]);
return (
<div className="card p-3 h-100 text-center d-flex justify-content-between">
<div className="d-flex justify-content-start align-items-center mb-3">
@ -14,13 +34,13 @@ const Teams = () => {
<div className="d-flex justify-content-around align-items-start mt-n2">
<div>
<h4 className="mb-0 fw-bold">
{teamsCardData.totalEmployees?.toLocaleString()}
{totalEmployees?.toLocaleString()}
</h4>
<small className="text-muted">Total Employees</small>
</div>
<div>
<h4 className="mb-0 fw-bold">
{teamsCardData.inToday?.toLocaleString()}
{inToday?.toLocaleString()}
</h4>
<small className="text-muted">In Today</small>
</div>

View File

@ -1,5 +1,9 @@
import getGreetingMessage from "../../utils/greetingHandler";
import { clearAllCache } from "../../slices/apiDataManager";
import {
cacheData,
clearAllCache,
getCachedData,
} from "../../slices/apiDataManager";
import AuthRepository from "../../repositories/AuthRepository";
import { useDispatch, useSelector } from "react-redux";
import { changeMaster, setProjectId } from "../../slices/localVariablesSlice";
@ -9,8 +13,11 @@ import { useLocation, useNavigate, useParams } from "react-router-dom";
import Avatar from "../../components/common/Avatar";
import { useChangePassword } from "../Context/ChangePasswordContext";
import { useProjects } from "../../hooks/useProjects";
import { useEffect, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import { useProjectName } from "../../hooks/useProjects";
import eventBus from "../../services/eventBus";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { MANAGE_PROJECT } from "../../utils/constants";
const Header = () => {
const { profile } = useProfile();
@ -18,6 +25,7 @@ const Header = () => {
const dispatch = useDispatch(changeMaster("Job Role"));
const { data, loading } = useMaster();
const navigate = useNavigate();
const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT);
const getRole = (roles, joRoleId) => {
if (!Array.isArray(roles)) return "User";
@ -64,7 +72,7 @@ const Header = () => {
navigate(`/employee/${profile?.employeeInfo?.id}?for=attendance`);
};
// const { projects, loading: projectLoading } = useProjects();
const { projectNames, loading: projectLoading } = useProjectName();
const { projectNames, loading: projectLoading, fetchData } = useProjectName();
const selectedProject = useSelector(
(store) => store.localVariables.projectId
@ -85,7 +93,11 @@ const Header = () => {
const { openChangePassword } = useChangePassword();
useEffect(() => {
if (projectNames && selectedProject !== " ") {
if (
projectNames &&
selectedProject !== " " &&
!getCachedData("hasReceived")
) {
dispatch(setProjectId(projectNames[0]?.id));
}
}, [projectNames]);
@ -93,6 +105,44 @@ const Header = () => {
/** Check if current page id project details page */
const isProjectPath = /^\/projects\/[a-f0-9-]{36}$/.test(location.pathname);
const handler = useCallback(
async (data) => {
if (!HasManageProjectPermission) {
await fetchData();
const projectExist = data.projectIds.some(
(item) => item == selectedProject
);
if (projectExist) {
cacheData("hasReceived", false);
}
}
},
[fetchData,projectNames,selectedProject]
);
useEffect(() => {
eventBus.on("assign_project_one", handler);
return () => eventBus.off("assign_project_one", handler);
}, [handler]);
const newProjectHandler = useCallback(
async (msg) => {
if (HasManageProjectPermission && msg.keyword === "Create_Project") {
await fetchData();
} else if (projectNames.some((item) => item.id == msg.response.id)) {
console.log((projectNames.some((item) => item.id == msg.response.id)))
await fetchData();
}
cacheData("hasReceived", false);
},
[HasManageProjectPermission,projectNames]
);
useEffect(() => {
eventBus.on("project", newProjectHandler);
return () => eventBus.off("project", newProjectHandler);
}, [handler]);
return (
<nav
className="layout-navbar container-xxl navbar navbar-expand-xl navbar-detached align-items-center bg-navbar-theme"
@ -169,7 +219,6 @@ const Header = () => {
<li className="nav-item dropdown-shortcuts navbar-dropdown dropdown me-2 me-xl-0">
<a
className="nav-link dropdown-toggle hide-arrow"
data-bs-toggle="dropdown"
data-bs-auto-close="true"
aria-expanded="false"

View File

@ -1,9 +1,13 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import moment from "moment";
import { getProjectStatusName } from "../../utils/projectStatus";
const AboutProject = ({ data }) => {
const [CurrentProject, setCurrentProject] = useState(data);
useEffect(() => {
setCurrentProject(data);
}, [data]);
return (
<>
{data && (

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from "react";
import React, { useState, useEffect, useRef, useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import { changeMaster } from "../../slices/localVariablesSlice";
import useMaster from "../../hooks/masterHook/useMaster";
@ -10,6 +10,7 @@ import { useEmployeesAllOrByProjectId } from "../../hooks/useEmployees";
import { TasksRepository } from "../../repositories/ProjectRepository";
import showToast from "../../services/toastService";
import { useProjectDetails } from "../../hooks/useProjects";
import eventBus from "../../services/eventBus";
const AssignTask = ({ assignData, onClose, setAssigned }) => {
const maxPlanned =
@ -75,10 +76,11 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
const selectedProject = useSelector(
(store) => store.localVariables.projectId
);
const { employees, loading: employeeLoading } = useEmployeesAllOrByProjectId(
selectedProject,
false
);
const {
employees,
loading: employeeLoading,
recallEmployeeData,
} = useEmployeesAllOrByProjectId(selectedProject, false);
const dispatch = useDispatch();
const { loading } = useMaster(); // Assuming this is for jobRoleData loading
const jobRoleData = getCachedData("Job Role");

View File

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

View File

@ -1,11 +1,17 @@
import { useEffect, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import Building from "./Building";
import Floor from "./Floor";
import FloorModel from "./FloorModel";
import showToast from "../../../services/toastService";
import ProjectRepository from "../../../repositories/ProjectRepository";
import eventBus from "../../../services/eventBus";
import {
cacheData,
clearCacheKey,
getCachedData,
} from "../../../slices/apiDataManager";
const InfraTable = ({ buildings }) => {
const InfraTable = ({ buildings, projectId, signalRHandler }) => {
const [projectBuilding, setProjectBuilding] = useState([]);
const [expandedBuildings, setExpandedBuildings] = useState([]);
const [showFloorModal, setShowFloorModal] = useState(false);
@ -108,12 +114,41 @@ const InfraTable = ({ buildings }) => {
);
};
useEffect(() => {
if (buildings && buildings.length > 0) {
setProjectBuilding(buildings);
setExpandedBuildings([buildings[0].id]);
}
if (buildings && buildings.length > 0) {
setProjectBuilding(buildings);
setExpandedBuildings([buildings[0].id]);
}
}, [buildings]);
const handler = useCallback(
(msg) => {
if (msg.projectIds.some((item) => item == projectId)) {
try {
ProjectRepository.getProjectByprojectId(projectId)
.then((response) => {
cacheData("projectInfo", {
projectId: projectId,
data: response.data,
});
setProjectBuilding(response?.data?.buildings);
signalRHandler?.(response?.data);
showToast(msg.message, "info");
})
.catch((error) => {
console.error(error);
});
} catch (e) {
console.error(e);
}
}
},
[buildings]
);
useEffect(() => {
eventBus.on("infra", handler);
return () => eventBus.off("infra", handler);
}, [handler]);
return (
<div>
{projectBuilding && projectBuilding.length > 0 && (

View File

@ -263,7 +263,7 @@ const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => {
<option value="b74da4c2-d07e-46f2-9919-e75e49b12731">Active</option>
<option value="603e994b-a27f-4e5d-a251-f3d69b0498ba">On Hold</option>
{/* <option value="3">Suspended</option> */}
<option value="cdad86aa-8a56-4ff4-b633-9c629057dfef">In Progress</option>
<option value="ef1c356e-0fe0-42df-a5d3-8daee355492d">Inactive</option>
<option value="33deaef9-9af1-4f2a-b443-681ea0d04f81">Completed</option>

View File

@ -117,7 +117,7 @@ const MapUsers = ({
<div className="modal-dialog modal-dialog-scrollable mx-sm-auto mx-1 modal-lg modal-simple modal-edit-user">
<div className="modal-content">
<div className="modal-header text-center">
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close">
</button>
</div>
<p className="m-0 fw-semibold fs-5">Assign Employee</p>

View File

@ -12,10 +12,15 @@ const ProjectBanner = ({ project_data }) => {
const [showModal, setShowModal] = useState(false);
const manageProject = useHasUserPermission(MANAGE_PROJECT);
const [CurrentProject, setCurrentProject] = useState(project_data);
if (project_data == null) {
return <span>incomplete project information</span>;
}
useEffect(() => {
setCurrentProject(project_data);
}, [project_data]);
const handleShow = () => setShowModal(true);
const handleClose = () => setShowModal(false);

View File

@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import moment from "moment";
import { getDateDifferenceInDays } from "../../utils/dateUtils";
import { useNavigate } from "react-router-dom";
@ -22,6 +22,10 @@ const ProjectCard = ({ projectData, recall }) => {
const ManageProject = useHasUserPermission(MANAGE_PROJECT);
const [modifyProjectLoading, setMdifyProjectLoading] = useState(false);
useEffect(()=>{
setProjectInfo(projectData);
},[projectData])
// console.log("in card view",projectInfo);
const handleShow = async () => {
try {
setMdifyProjectLoading(true);

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useCallback } from "react";
import "./ProjectInfra.css";
import BuildingModel from "./Infrastructure/BuildingModel";
import FloorModel from "./Infrastructure/FloorModel";
@ -12,20 +12,20 @@ import ProjectModal from "./ProjectModal";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { MANAGE_PROJECT_INFRA } from "../../utils/constants";
import InfraTable from "./Infrastructure/InfraTable";
import { cacheData, clearCacheKey, getCachedData } from "../../slices/apiDataManager";
import {
cacheData,
clearCacheKey,
getCachedData,
} from "../../slices/apiDataManager";
import { useProjectDetails } from "../../hooks/useProjects";
import {useDispatch, useSelector} from "react-redux";
import {refreshData} from "../../slices/localVariablesSlice";
import { useDispatch, useSelector } from "react-redux";
import { refreshData } from "../../slices/localVariablesSlice";
import eventBus from "../../services/eventBus";
const ProjectInfra = ({
data,
onDataChange,
eachSiteEngineer,
} ) =>
{
const reloadedData = useSelector((store)=>store.localVariables.reload)
const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
const reloadedData = useSelector((store) => store.localVariables.reload);
const [expandedBuildings, setExpandedBuildings] = useState([]);
const { projects_Details,refetch, loading } = useProjectDetails(data?.id);
const { projects_Details, refetch, loading } = useProjectDetails(data?.id);
const [project, setProject] = useState(projects_Details);
const [modalConfig, setModalConfig] = useState({ type: null, data: null });
const [isModalOpen, setIsModalOpen] = useState(false);
@ -39,8 +39,8 @@ const ProjectInfra = ({
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [clearFormTrigger, setClearFormTrigger] = useState(false);
const [CurrentBuilding, setCurrentBuilding] = useState("");
const [ showModal, setShowModal ] = useState( false );
const dispatch = useDispatch()
const [showModal, setShowModal] = useState(false);
const dispatch = useDispatch();
useEffect(() => {
setProject(projects_Details);
@ -143,8 +143,8 @@ const ProjectInfra = ({
existingItem.workItemId ===
workItem.workItemId
)
? [...workArea.workItems] // Create a new array to trigger re-render
: [...workArea.workItems, workItem],
? [...workArea.workItems] // Create a new array to trigger re-render
: [...workArea.workItems, workItem],
}
: workArea
),
@ -156,15 +156,15 @@ const ProjectInfra = ({
);
updatedProject.buildings = updatedBuildings;
// workItem update, but having local state issue there for needed to calling api
clearCacheKey( "projectInfo" )
refetch()
clearCacheKey("projectInfo");
refetch();
cacheData("projectInfo", {
projectId: updatedProject.id,
data: updatedProject,
});
setProject( updatedProject );
setProject(updatedProject);
// closeTaskModel()
}
})
@ -173,11 +173,8 @@ const ProjectInfra = ({
});
};
const submitData = async (infraObject) => {
try
{
try {
let response = await ProjectRepository.manageProjectInfra(infraObject);
const entity = response.data;
@ -209,9 +206,8 @@ const ProjectInfra = ({
setProject((prevProject) => ({
...prevProject,
buildings: updatedBuildings,
} ) );
}));
// closeBuildingModel()
}
// Handle the floor data
else if (entity.floor) {
@ -247,12 +243,11 @@ const ProjectInfra = ({
projectId: updatedProject.id,
data: updatedProject,
});
setProject( updatedProject );
setProject(updatedProject);
// closeFloorModel()
}
// Handle the work area data
else if ( entity.workArea )
{
else if (entity.workArea) {
let buildingId = infraObject[0].workArea.buildingId;
const { floorId, areaName, id } = entity.workArea;
@ -291,7 +286,7 @@ const ProjectInfra = ({
projectId: updatedProject.id,
data: updatedProject,
});
setProject( updatedProject );
setProject(updatedProject);
// closeWorkAreaModel()
}
// Handle the task (workItem) data
@ -303,7 +298,6 @@ const ProjectInfra = ({
}
};
const toggleBuilding = (id) => {
setExpandedBuildings((prev) =>
prev.includes(id) ? prev.filter((bid) => bid !== id) : [...prev, id]
@ -344,15 +338,17 @@ const ProjectInfra = ({
};
const handleShow = () => setShowModal(true);
const handleClose = () => setShowModal( false );
useEffect( () =>
{
if (reloadedData)
{
refetch()
dispatch( refreshData( false ) )
}
},[reloadedData])
const handleClose = () => setShowModal(false);
useEffect(() => {
if (reloadedData) {
refetch();
dispatch(refreshData(false));
}
}, [reloadedData]);
const signalRHandler = (response) => {
setProject(response);
}
return (
<>
@ -420,7 +416,6 @@ const ProjectInfra = ({
>
<TaskModel
project={project}
onClose={closeTaskModel}
onSubmit={handleTaskModelFormSubmit}
clearTrigger={clearFormTrigger}
@ -482,8 +477,9 @@ const ProjectInfra = ({
{project && project.buildings?.length > 0 && (
<InfraTable
buildings={project?.buildings}
project={project}
projectId={project.id}
handleFloor={submitData}
signalRHandler = {signalRHandler}
/>
)}
</div>

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useCallback } from "react";
import MapUsers from "./MapUsers";
import { Link, NavLink, useNavigate } from "react-router-dom";
@ -13,6 +13,7 @@ import useMaster from "../../hooks/masterHook/useMaster";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { ASSIGN_TO_PROJECT } from "../../utils/constants";
import ConfirmModal from "../common/ConfirmModal";
import eventBus from "../../services/eventBus";
const Teams = ({ project }) => {
const dispatch = useDispatch();
@ -173,6 +174,33 @@ const Teams = ({ project }) => {
}
const closeDeleteModal = ()=> setIsDeleteModal(false)
const handler = useCallback(
(msg) => {
if (msg.projectIds.some((item) => item === project.id)) {
fetchEmployees();
}
},
[]
);
useEffect(() => {
eventBus.on("assign_project_all", handler);
return () => eventBus.off("assign_project_all", handler);
}, [handler]);
const employeeHandler = useCallback(
(msg) => {
if(filteredEmployees.some((item) => item.employeeId == msg.employeeId)){
fetchEmployees();
}
},[filteredEmployees]
);
useEffect(() => {
eventBus.on("employee",employeeHandler);
return () => eventBus.off("employee",employeeHandler)
},[employeeHandler])
return (
<>
<div
@ -322,10 +350,10 @@ const Teams = ({ project }) => {
{" "}
{removingEmployeeId === item.id ? (
<div
class="spinner-border spinner-border-sm text-primary"
className="spinner-border spinner-border-sm text-primary"
role="status"
>
<span class="visually-hidden">
<span className="visually-hidden">
Loading...
</span>
</div>

View File

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

View File

@ -1,9 +1,11 @@
import {useState,useEffect} from "react";
import {useState,useEffect, useCallback} from "react";
import AuthRepository from "../repositories/AuthRepository";
import {cacheProfileData, getCachedProfileData} from "../slices/apiDataManager";
import {cacheData, cacheProfileData, getCachedData, getCachedProfileData} from "../slices/apiDataManager";
import {useSelector} from "react-redux";
import eventBus from "../services/eventBus";
let hasFetched = false;
let hasReceived = false;
export const useProfile = () => {
const loggedUser = useSelector( ( store ) => store.globalVariables.loginUser );
@ -24,7 +26,7 @@ export const useProfile = () => {
}
};
useEffect(() => {
const validation = () => {
if (!hasFetched) {
hasFetched = true;
if (!loggedUser) {
@ -35,8 +37,26 @@ export const useProfile = () => {
}
setProfile(loggedUser);
}
useEffect(() => {
validation();
}, [loggedUser]);
const handler = useCallback(
(data) => {
if(!getCachedData("hasReceived")){
cacheData("hasReceived", true);
hasFetched = false;
validation();
}
},[]
);
useEffect(() => {
eventBus.on("assign_project_one", handler);
return () => eventBus.off("assign_project_one", handler);
}, [handler]);
return { profile, loading, error };
};

View File

@ -1,10 +1,11 @@
import { useEffect, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import { cacheData, getCachedData } from "../slices/apiDataManager";
import ProjectRepository from "../repositories/ProjectRepository";
import { useProfile } from "./useProfile";
import { useDispatch, useSelector } from "react-redux";
import { setProjectId } from "../slices/localVariablesSlice";
import EmployeeList from "../components/Directory/EmployeeList";
import eventBus from "../services/eventBus";
export const useProjects = () => {
const loggedUser = useSelector((store) => store.globalVariables.loginUser);
@ -174,6 +175,7 @@ export const useProjectName = () => {
const [loading, setLoading] = useState(true);
const [projectNames, setProjectName] = useState([]);
const [Error, setError] = useState();
const dispatch = useDispatch();
const fetchData = async () => {
try {
@ -181,6 +183,9 @@ export const useProjectName = () => {
setProjectName(response.data);
cacheData("basicProjectNameList", response.data);
setLoading(false);
if(response.data.length === 1){
dispatch(setProjectId(response.data[0]?.id));
}
} catch (err) {
setError("Failed to fetch data.");
setLoading(false);
@ -190,5 +195,5 @@ export const useProjectName = () => {
fetchData();
}, []);
return { projectNames, loading, Error };
return { projectNames, loading, Error, fetchData };
};

View File

@ -2,15 +2,26 @@ import React, { useEffect } from "react";
import { Outlet } from "react-router-dom";
import Header from "../components/Layout/Header";
import Sidebar from "../components/Layout/Sidebar";
import { startSignalR, stopSignalR } from "../services/signalRService";
import Footer from "../components/Layout/Footer";
import FloatingMenu from "../components/common/FloatingMenu";
import { FabProvider } from "../Context/FabContext";
import { useSelector } from "react-redux";
const HomeLayout = () => {
const loggedUser = useSelector((store) => store.globalVariables.loginUser);
useEffect(() => {
Main();
}, []);
useEffect(() => {
if (loggedUser) {
startSignalR(loggedUser);
}
return () => {
stopSignalR();
};
}, [loggedUser]);
return (
<FabProvider>
<div className="layout-wrapper layout-content-navbar">

View File

@ -1,6 +1,7 @@
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useCallback } from "react";
import {
cacheData,
clearCacheKey,
getCachedData,
getCachedProfileData,
} from "../../slices/apiDataManager";
@ -18,6 +19,8 @@ import { markCurrentAttendance } from "../../slices/apiSlice/attendanceAllSlice"
import { hasUserPermission } from "../../utils/authUtils";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { REGULARIZE_ATTENDANCE } from "../../utils/constants";
import eventBus from "../../services/eventBus";
import AttendanceRepository from "../../repositories/AttendanceRepository";
const AttendancePage = () => {
const [activeTab, setActiveTab] = useState("all");
@ -25,7 +28,11 @@ const AttendancePage = () => {
const loginUser = getCachedProfileData();
var selectedProject = useSelector((store) => store.localVariables.projectId);
// const { projects, loading: projectLoading } = useProjects();
const { attendance, loading: attLoading } = useAttendace(selectedProject);
const {
attendance,
loading: attLoading,
recall: attrecall,
} = useAttendace(selectedProject);
const [attendances, setAttendances] = useState();
const [empRoles, setEmpRoles] = useState(null);
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
@ -39,6 +46,40 @@ const AttendancePage = () => {
date: new Date().toLocaleDateString(),
});
const handler = useCallback(
(msg) => {
if (selectedProject == msg.projectId) {
const updatedAttendance = attendances.map((item) =>
item.employeeId === msg.response.employeeId
? { ...item, ...msg.response }
: item
);
cacheData("Attendance", {
data: updatedAttendance,
projectId: selectedProject,
});
setAttendances(updatedAttendance);
}
},
[selectedProject, attrecall]
);
const employeeHandler = useCallback(
(msg) => {
if (attendances.some((item) => item.employeeId == msg.employeeId)) {
AttendanceRepository.getAttendance(selectedProject)
.then((response) => {
cacheData("Attendance", { data: response.data, selectedProject });
setAttendances(response.data);
})
.catch((error) => {
console.error(error);
});
}
},
[selectedProject, attendances]
);
const getRole = (roleId) => {
if (!empRoles) return "Unassigned";
if (!roleId) return "Unassigned";
@ -112,9 +153,20 @@ const AttendancePage = () => {
// )
// : attendances;
const filteredAttendance = ShowPending
? attendances?.filter((att) => att?.checkInTime !== null && att?.checkOutTime === null)
? attendances?.filter(
(att) => att?.checkInTime !== null && att?.checkOutTime === null
)
: attendances;
useEffect(() => {
eventBus.on("attendance", handler);
return () => eventBus.off("attendance", handler);
}, [handler]);
useEffect(() => {
eventBus.on("employee", employeeHandler);
return () => eventBus.off("employee", employeeHandler);
}, [employeeHandler]);
return (
<>
{isCreateModalOpen && modelConfig && (
@ -213,15 +265,10 @@ const AttendancePage = () => {
Regularization
</button>
</li>
</ul>
<div className="tab-content attedanceTabs py-0 px-1 px-sm-3">
{activeTab === "all" && (
<>
<div className="tab-pane fade show active py-0">
<Attendance
attendance={filteredAttendance}
@ -231,8 +278,13 @@ const AttendancePage = () => {
showOnlyCheckout={ShowPending}
/>
</div>
{!attLoading && filteredAttendance?.length === 0 && (
<p> {ShowPending ? "No Pending Available" : "No Employee assigned yet."} </p>
{!attLoading && filteredAttendance?.length === 0 && (
<p>
{" "}
{ShowPending
? "No Pending Available"
: "No Employee assigned yet."}{" "}
</p>
)}
</>
)}
@ -254,7 +306,7 @@ const AttendancePage = () => {
</div>
)}
{attLoading && <span>Loading..</span>}
{attLoading && <span>Loading..</span>}
{!attLoading && !attendances && <span>Not Found</span>}
</div>
</div>

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from "react";
import React, { useState, useEffect, useRef, useCallback } from "react";
import moment from "moment";
import showToast from "../../services/toastService";
import { Link, NavLink, useNavigate } from "react-router-dom";
@ -23,6 +23,8 @@ import EmployeeRepository from "../../repositories/EmployeeRepository";
import ManageEmployee from "../../components/Employee/ManageEmployee";
import ConfirmModal from "../../components/common/ConfirmModal";
import { useSelector } from "react-redux";
import eventBus from "../../services/eventBus";
import { newlineChars } from "pdf-lib";
const EmployeeList = () => {
const selectedProjectId = useSelector((store) => store.localVariables.projectId);
@ -202,7 +204,6 @@ const EmployeeList = () => {
};
const handleOpenDelete = (employee) => {
console.log(employee);
setSelectedEmpFordelete(employee);
setIsDeleteModalOpen(true);
};
@ -218,6 +219,20 @@ const EmployeeList = () => {
setSelectedProject(selectedProjectId || "");
}, [selectedProjectId]);
const handler = useCallback(
(msg) => {
if(employees.some((item) => item.id == msg.employeeId)){
setEmployeeList([]);
recallEmployeeData(showInactive);
}
},[employees]
);
useEffect(() => {
eventBus.on("employee",handler);
return () => eventBus.off("employee",handler)
},[handler])
return (
<>

View File

@ -1,5 +1,5 @@
import { useParams } from "react-router-dom";
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useCallback } from "react";
import ActivityTimeline from "../../components/Project/ActivityTimeline";
import ProjectOverview from "../../components/Project/ProjectOverview";
@ -11,7 +11,7 @@ import ProjectInfra from "../../components/Project/ProjectInfra";
import Loader from "../../components/common/Loader";
import WorkPlan from "../../components/Project/WorkPlan";
import Breadcrumb from "../../components/common/Breadcrumb";
import { cacheData, getCachedData } from "../../slices/apiDataManager";
import { cacheData, clearCacheKey, getCachedData } from "../../slices/apiDataManager";
import ProjectRepository from "../../repositories/ProjectRepository";
import { ActivityeRepository } from "../../repositories/MastersRepository";
import "./ProjectDetails.css";
@ -23,6 +23,7 @@ import { useDispatch } from "react-redux";
import { setProjectId } from "../../slices/localVariablesSlice";
import { ComingSoonPage } from "../Misc/ComingSoonPage";
import Directory from "../Directory/Directory";
import eventBus from "../../services/eventBus";
const ProjectDetails = () => {
let { projectId } = useParams();
@ -121,7 +122,7 @@ const ProjectDetails = () => {
case "directory": {
return (
<div className="row">
<Directory IsPage={ false} prefernceContacts={projectDetails.id} />
<Directory IsPage={false} prefernceContacts={projectDetails.id} />
</div>
);
}
@ -137,6 +138,31 @@ const ProjectDetails = () => {
setProjectDetails(projects_Details);
}, [projects_Details, projectId]);
const handler = useCallback(
(msg) => {
if (msg.keyword === "Update_Project" && project.id === msg.response.id) {
clearCacheKey("projectInfo")
ProjectRepository.getProjectByprojectId(projectId)
.then((response) => {
setProjectDetails(response.data);
setProject(response.data);
cacheData("projectInfo", { projectId, data: response.data });
setLoading(false);
})
.catch((error) => {
console.error(error);
setError("Failed to fetch data.");
setLoading(false);
});
}
},
[project,handleDataChange]
);
useEffect(() => {
eventBus.on("project", handler);
return () => eventBus.off("project", handler);
}, [handler]);
return (
<>
{}

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useCallback } from "react";
import ProjectCard from "../../components/Project/ProjectCard";
import ManageProjectInfo from "../../components/Project/ManageProjectInfo";
import Breadcrumb from "../../components/common/Breadcrumb";
@ -6,11 +6,14 @@ import ProjectRepository from "../../repositories/ProjectRepository";
import { useProjects } from "../../hooks/useProjects";
import { useDispatch } from "react-redux";
import showToast from "../../services/toastService";
import { getCachedData, cacheData } from "../../slices/apiDataManager";
import { getCachedData, cacheData, clearCacheKey } from "../../slices/apiDataManager";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { useProfile } from "../../hooks/useProfile";
import { ITEMS_PER_PAGE, MANAGE_PROJECT } from "../../utils/constants";
import ProjectListView from "./ProjectListView";
import eventBus from "../../services/eventBus";
import { clearApiCacheKey } from "../../slices/apiCacheSlice";
import { defaultCheckBoxAppearanceProvider } from "pdf-lib";
const ProjectList = () => {
const { profile: loginUser } = useProfile();
@ -37,7 +40,7 @@ const ProjectList = () => {
const handleShow = () => setShowModal(true);
const handleClose = () => setShowModal(false);
const sortingProject = (projects) =>{
const sortingProject = (projects) => {
if (!loading && Array.isArray(projects)) {
const grouped = {};
projects.forEach((project) => {
@ -53,13 +56,12 @@ const ProjectList = () => {
a.name.toLowerCase()?.localeCompare(b.name.toLowerCase())
)
);
setProjectList(sortedGrouped);
}
}
};
useEffect(() => {
sortingProject(projects)
sortingProject(projects);
}, [projects, loginUser?.projects, loading]);
useEffect(() => {
@ -70,16 +72,16 @@ const ProjectList = () => {
}
}, [loginUser, HasManageProjectPermission]);
const handleSubmitForm = (newProject,setloading,reset) => {
const handleSubmitForm = (newProject, setloading, reset) => {
ProjectRepository.manageProject(newProject)
.then((response) => {
const cachedProjects = getCachedData("projectslist") || [];
const updatedProjects = [...cachedProjects, response.data];
cacheData("projectslist", updatedProjects);
setProjectList( ( prev ) => [ ...prev, response.data ] );
setloading( false )
reset()
sortingProject(getCachedData("projectslist"))
setProjectList((prev) => [...prev, response.data]);
setloading(false);
reset();
sortingProject(getCachedData("projectslist"));
showToast("Project Created successfully.", "success");
setShowModal(false);
})
@ -123,7 +125,6 @@ const ProjectList = () => {
indexOfLastItem
);
const totalPages = Math.ceil(filteredProjects.length / itemsPerPage);
useEffect(() => {
const tooltipTriggerList = Array.from(
document.querySelectorAll('[data-bs-toggle="tooltip"]')
@ -131,6 +132,52 @@ const ProjectList = () => {
tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el));
}, []);
const handler = useCallback(
async (msg) => {
if (HasManageProject && msg.keyword === "Create_Project") {
const updatedProjects = [...projectList, msg.response];
cacheData("projectslist", updatedProjects);
setProjectList(updatedProjects);
sortingProject(updatedProjects);
}
if (
msg.keyword === "Update_Project" &&
projectList.some((item) => item.id === msg.response.id)
) {
ProjectRepository.getProjectList()
.then((response) => {
cacheData("projectslist", response?.data);
sortingProject(response?.data);
})
.catch((e) => {
console.error(e)
});
}
},
[HasManageProject, projectList, sortingProject]
);
useEffect(() => {
eventBus.on("project", handler);
return () => eventBus.off("project", handler);
}, [handler]);
const assignProjectHandler = useCallback(
async (data) => {
clearCacheKey("projectslist");
await refetch();
sortingProject(projects);
},
[refetch]
);
useEffect(() => {
eventBus.on("assign_project_one", assignProjectHandler);
return () => eventBus.off("assign_project_one", assignProjectHandler);
}, [handler]);
return (
<>
<div
@ -200,49 +247,48 @@ const ProjectList = () => {
<i className="bx bx-list-ul bx-sm"></i>
</button>
</div>
<div className="dropdown ms-3">
<a
className="dropdown-toggle hide-arrow cursor-pointer"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<i className="bx bx-filter bx-lg"></i>
</a>
<ul className="dropdown-menu p-2 text-capitalize">
{[
{
id: "b74da4c2-d07e-46f2-9919-e75e49b12731",
label: "Active",
},
{
id: "603e994b-a27f-4e5d-a251-f3d69b0498ba",
label: "On Hold",
},
{
id: "ef1c356e-0fe0-42df-a5d3-8daee355492d",
label: "Inactive",
},
{
id: "33deaef9-9af1-4f2a-b443-681ea0d04f81",
label: "Completed",
},
].map(({ id, label }) => (
<li key={id}>
<div className="form-check">
<input
className="form-check-input "
type="checkbox"
checked={selectedStatuses.includes(id)}
onChange={() => handleStatusChange(id)}
/>
<label className="form-check-label">{label}</label>
</div>
</li>
))}
</ul>
</div>
<div className="dropdown ms-3">
<a
className="dropdown-toggle hide-arrow cursor-pointer"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<i className="bx bx-filter bx-lg"></i>
</a>
<ul className="dropdown-menu p-2 text-capitalize">
{[
{
id: "b74da4c2-d07e-46f2-9919-e75e49b12731",
label: "Active",
},
{
id: "603e994b-a27f-4e5d-a251-f3d69b0498ba",
label: "On Hold",
},
{
id: "ef1c356e-0fe0-42df-a5d3-8daee355492d",
label: "Inactive",
},
{
id: "33deaef9-9af1-4f2a-b443-681ea0d04f81",
label: "Completed",
},
].map(({ id, label }) => (
<li key={id}>
<div className="form-check">
<input
className="form-check-input "
type="checkbox"
checked={selectedStatuses.includes(id)}
onChange={() => handleStatusChange(id)}
/>
<label className="form-check-label">{label}</label>
</div>
</li>
))}
</ul>
</div>
</div>
<div>
@ -341,7 +387,11 @@ const ProjectList = () => {
</tr>
) : (
currentItems.map((project) => (
<ProjectListView key={project.id} projectData={project} recall={sortingProject} />
<ProjectListView
key={project.id}
projectData={project}
recall={sortingProject}
/>
))
)}
</tbody>
@ -349,7 +399,11 @@ const ProjectList = () => {
</div>
) : (
currentItems.map((project) => (
<ProjectCard key={project.id} projectData={project} recall={sortingProject} />
<ProjectCard
key={project.id}
projectData={project}
recall={sortingProject}
/>
))
)}
</div>

View File

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

5
src/services/eventBus.js Normal file
View File

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

View File

@ -0,0 +1,106 @@
import * as signalR from "@microsoft/signalr";
import {
cacheData,
clearCacheKey,
getCachedData,
} from "../slices/apiDataManager";
import showToast from "./toastService";
import eventBus from "./eventBus";
import { useSelector } from "react-redux";
import { clearApiCacheKey } from "../slices/apiCacheSlice";
const base_Url = process.env.VITE_BASE_URL;
// const base_Url = "https://devapi.marcoaiot.com";
let connection = null;
const targetPath = "";
export function startSignalR(loggedUser) {
var jwtToken = localStorage.getItem("jwtToken");
connection = new signalR.HubConnectionBuilder()
.withUrl(`${base_Url}/hubs/marco`, {
accessTokenFactory: () => jwtToken,
transport: signalR.HttpTransportType.LongPolling,
withCredentials: false,
})
.withAutomaticReconnect()
.build();
const todayDate = new Date();
const today = new Date(
Date.UTC(todayDate.getFullYear(), todayDate.getMonth(), todayDate.getDate())
)
.toISOString()
.split("T")[0];
connection.on("NotificationEventHandler", (data) => {
if (data.loggedInUserId != loggedUser?.employeeInfo.id) {
// console.log("Notification received:", data);
// if action taken on attendance module
if (data.keyword == "Attendance") {
const checkIn = data.response.checkInTime.substring(0, 10);
if (today === checkIn) {
eventBus.emit("attendance", data);
}
var onlyDate = Number(checkIn.substring(8, 10));
var afterTwoDay =
checkIn.substring(0, 8) + (onlyDate + 2).toString().padStart(2, "0");
if (
afterTwoDay <= today &&
(data.response.activity == 4 || data.response.activity == 5)
) {
eventBus.emit("regularization", data);
}
eventBus.emit("attendance_log", data);
}
// if create or update project
if (
data.keyword == "Create_Project" ||
data.keyword == "Update_Project"
) {
clearCacheKey("projectslist");
eventBus.emit("project", data);
}
// if assign or deassign employee to any project
if (data.keyword == "Assign_Project") {
if (
data.employeeList.some((item) => item === loggedUser?.employeeInfo.id)
) {
try {
cacheData("hasReceived", false);
eventBus.emit("assign_project_one", data);
} catch (e) {
console.error("Error in cacheData:", e);
}
}
eventBus.emit("assign_project_all", data);
}
// if created or updated infra
if (data.keyword == "Infra") {
clearCacheKey("projectInfo");
eventBus.emit("infra", data);
}
// if created or updated Employee
if (data.keyword == "Employee") {
clearCacheKey("employeeListByProject");
clearCacheKey("allEmployeeList");
clearCacheKey("allInactiveEmployeeList");
clearCacheKey("employeeProfile");
clearCacheKey("Attendance");
clearCacheKey("regularizedList")
clearCacheKey("AttendanceLogs")
eventBus.emit("employee", data);
}
}
});
connection
.start()
.then(() => console.log("SignalR connected"))
.catch((err) => console.error("SignalR error:", err));
}
export function stopSignalR() {
if (connection) connection.stop();
}

View File

@ -2,6 +2,7 @@ import axios from "axios";
import { useNavigate } from "react-router-dom";
import axiosRetry from "axios-retry";
import showToast from "../services/toastService";
import { startSignalR, stopSignalR } from "../services/signalRService";
const base_Url = process.env.VITE_BASE_URL;
// const base_Url = "https://api.marcoaiot.com";
export const axiosClient = axios.create({
@ -69,6 +70,8 @@ axiosClient.interceptors.response.use(
return Promise.reject(error);
}
stopSignalR();
try {
// Refresh token
const res = await axiosClient.post("/api/Auth/refresh-token", {
@ -82,6 +85,7 @@ axiosClient.interceptors.response.use(
localStorage.setItem("jwtToken", token);
localStorage.setItem("refreshToken", newRefreshToken);
startSignalR()
// Set Authorization header
originalRequest.headers["Authorization"] = `Bearer ${token}`;

View File

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