Implmeneted signalR in today's attendance

This commit is contained in:
ashutosh.nehete 2025-06-11 10:20:53 +05:30
parent 6f88980986
commit a3381bb2e6
8 changed files with 274 additions and 11 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

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

@ -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,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useCallback } from "react";
import {
cacheData,
getCachedData,
@ -18,14 +18,19 @@ 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";
const AttendancePage = () => {
const [activeTab, setActiveTab] = useState("all");
const [ShowPending, setShowPending] = useState(false);
const loginUser = getCachedProfileData();
var selectedProject = useSelector((store) => store.localVariables.projectId);
// const { projects, loading: projectLoading } = useProjects();
const { attendance, loading: attLoading } = useAttendace(selectedProject);
const { projects, loading: projectLoading } = useProjects();
const {
attendance,
loading: attLoading,
recall: attrecall,
} = useAttendace(selectedProject);
const [attendances, setAttendances] = useState();
const [empRoles, setEmpRoles] = useState(null);
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
@ -39,6 +44,16 @@ const AttendancePage = () => {
date: new Date().toLocaleDateString(),
});
const handler = useCallback(
(msg) => {
console.log("Equal:", selectedProject == msg.projectId);
if (selectedProject == msg.projectId) {
attrecall();
}
},
[selectedProject, attrecall]
);
const getRole = (roleId) => {
if (!empRoles) return "Unassigned";
if (!roleId) return "Unassigned";
@ -115,6 +130,10 @@ const AttendancePage = () => {
? attendances?.filter((att) => att?.checkInTime !== null && att?.checkOutTime === null)
: attendances;
useEffect(() => {
eventBus.on("attendance", handler);
return () => eventBus.off("attendance", handler);
}, [handler]);
return (
<>
{isCreateModalOpen && modelConfig && (
@ -213,8 +232,6 @@ const AttendancePage = () => {
Regularization
</button>
</li>
</ul>
<div className="tab-content attedanceTabs py-0 px-1 px-sm-3">

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,37 @@
import * as signalR from "@microsoft/signalr";
import { clearCacheKey, getCachedData } from "../slices/apiDataManager";
import showToast from "./toastService";
import eventBus from "./eventBus";
import { useSelector } from "react-redux";
let connection = null;
const targetPath = "";
export function startSignalR(loggedUser) {
console.log(loggedUser?.employeeInfo.id);
connection = new signalR.HubConnectionBuilder()
.withUrl("http://localhost:5032/hubs/marco", {
accessTokenFactory: () => localStorage.getItem("jwtToken"),
withCredentials: false,
})
.withAutomaticReconnect()
.build();
connection.on("Attendance", (data) => {
clearCacheKey("Attendance");
if (data.loginUserId != loggedUser?.employeeInfo.id) {
eventBus.emit('attendance', 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}`;