diff --git a/package-lock.json b/package-lock.json index dfe50b1b..703df311 100644 --- a/package-lock.json +++ b/package-lock.json @@ -809,9 +809,9 @@ } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "license": "MIT", "peer": true, "dependencies": { @@ -1552,13 +1552,13 @@ "peer": true }, "node_modules/@types/node": { - "version": "22.13.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.13.tgz", - "integrity": "sha512-ClsL5nMwKaBRwPcCvH8E7+nU4GxHVx1axNvMZTFHMEfNI7oahimt26P5zjVCRrjiIWj6YFXfE1v3dEp94wLcGQ==", + "version": "24.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", + "integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==", "license": "MIT", "peer": true, "dependencies": { - "undici-types": "~6.20.0" + "undici-types": "~7.12.0" } }, "node_modules/@types/prop-types": { @@ -1835,9 +1835,10 @@ } }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -1845,6 +1846,19 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -2625,9 +2639,9 @@ "integrity": "sha512-ZpSAUOZ2Izby7qnZluSrAlGgGQzucmFbN0n64dYzocYxnxV5ufurpj3VgEe4cUp7ir9LmeLxNYo8bVnlM8bQHw==" }, "node_modules/enhanced-resolve": { - "version": "5.18.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", - "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", "license": "MIT", "peer": true, "dependencies": { @@ -2741,9 +2755,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", - "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", "license": "MIT", "peer": true }, @@ -3138,9 +3152,9 @@ "dev": true }, "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", "funding": [ { "type": "github", @@ -5163,9 +5177,9 @@ } }, "node_modules/schema-utils": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", - "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", + "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", "license": "MIT", "peer": true, "dependencies": { @@ -5567,24 +5581,28 @@ } }, "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz", + "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", "license": "MIT", "peer": true, "engines": { "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/terser": { - "version": "5.39.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", - "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz", + "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", "license": "BSD-2-Clause", "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", + "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -5777,9 +5795,9 @@ } }, "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", + "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==", "license": "MIT", "peer": true }, @@ -5907,9 +5925,9 @@ } }, "node_modules/watchpack": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", - "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", "license": "MIT", "peer": true, "dependencies": { @@ -5927,21 +5945,23 @@ "license": "BSD-2-Clause" }, "node_modules/webpack": { - "version": "5.98.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz", - "integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==", + "version": "5.101.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz", + "integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==", "license": "MIT", "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.6", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.14.0", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", + "enhanced-resolve": "^5.17.3", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -5951,11 +5971,11 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^4.3.0", + "schema-utils": "^4.3.2", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.3.11", "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" + "webpack-sources": "^3.3.3" }, "bin": { "webpack": "bin/webpack.js" @@ -5974,15 +5994,22 @@ } }, "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", "license": "MIT", "peer": true, "engines": { "node": ">=10.13.0" } }, + "node_modules/webpack/node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT", + "peer": true + }, "node_modules/webpack/node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", diff --git a/public/assets/css/core-extend.css b/public/assets/css/core-extend.css index 327febfa..68fd44df 100644 --- a/public/assets/css/core-extend.css +++ b/public/assets/css/core-extend.css @@ -1,8 +1,12 @@ :root, [data-bs-theme="light"] { --bs-nav-link-font-size: 0.7375rem; + --bg-border-color :#f8f6f6 } .card-header { padding: 0.5rem var(--bs-card-cap-padding-x); } +.table_header_border { + border-bottom:2px solid var(--bs-table-border-color) ; +} \ No newline at end of file diff --git a/public/assets/img/SP-Placeholdeer.svg b/public/assets/img/SP-Placeholdeer.svg new file mode 100644 index 00000000..695f4493 --- /dev/null +++ b/public/assets/img/SP-Placeholdeer.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index be7a72ba..db452672 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,7 @@ import { ToastContainer } from "react-toastify"; import { QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { queryClient } from "./layouts/AuthLayout"; +import ModalProvider from "./ModalProvider"; @@ -11,6 +12,7 @@ const App = () => { return (
+ diff --git a/src/ModalContext.jsx b/src/ModalContext.jsx deleted file mode 100644 index 72607465..00000000 --- a/src/ModalContext.jsx +++ /dev/null @@ -1,112 +0,0 @@ -import React, { - createContext, - useContext, - useState, - useEffect, - useRef, -} from "react"; - -const ModalContext = createContext(); - -export const useModal = () => useContext(ModalContext); - -// ModalProvider to manage modal state and expose functionality to the rest of the app -export const ModalProvider = ({ children }) => { - const [isOpen, setIsOpen] = useState(false); - const [modalContent, setModalContent] = useState(null); - const [onSubmit, setOnSubmit] = useState(null); - const [modalSize, setModalSize] = useState("lg"); - - // Ref to track the modal content element - const modalRef = useRef(null); - - const openModal = (content, onSubmitCallback, size = "lg") => { - setModalContent(content); // Set modal content dynamically - setOnSubmit(() => onSubmitCallback); // Set the submit handler dynamically - setIsOpen(true); // Open the modal - setModalSize(size); // Set the modal size - }; - - // Function to close the modal - const closeModal = () => { - setIsOpen(false); // Close the modal - setModalContent(null); // Clear modal content - setOnSubmit(null); // Clear the submit callback - setModalSize("lg"); // Reset modal size - }; - - useEffect(() => { - const handleEscape = (event) => { - if (event.key === "Escape") { - closeModal(); - } - }; - document.addEventListener("keydown", handleEscape); - return () => { - document.removeEventListener("keydown", handleEscape); - }; - }, []); - - useEffect(() => { - const handleClickOutside = (event) => { - if (modalRef.current && !modalRef.current.contains(event.target)) { - closeModal(); - } - }; - document.addEventListener("mousedown", handleClickOutside); - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; - }, []); - - return ( - - {children} - - {isOpen && ( -
-
-
{modalContent}
-
-
- )} -
- ); -}; - -const overlayStyles = { - position: "fixed", - top: 0, - left: 0, - right: 0, - bottom: 0, - backgroundColor: "rgba(0, 0, 0, 0.5)", - display: "flex", - justifyContent: "center", - alignItems: "center", - zIndex: 1050, -}; - -const modalStyles = { - backgroundColor: "white", - padding: "20px", - borderRadius: "5px", - boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)", - width: "90%", - maxWidth: "800px", -}; diff --git a/src/ModalProvider.jsx b/src/ModalProvider.jsx new file mode 100644 index 00000000..b9a2dea9 --- /dev/null +++ b/src/ModalProvider.jsx @@ -0,0 +1,19 @@ +import React, { useEffect } from "react"; +import { useOrganizationModal } from "./hooks/useOrganization"; +import OrganizationModal from "./components/Organization/OrganizationModal"; +import { useAuthModal } from "./hooks/useAuth"; +import SwitchTenant from "./pages/authentication/SwitchTenant"; + +const ModalProvider = () => { + const { isOpen, onClose } = useOrganizationModal(); + const { isOpen: isAuthOpen } = useAuthModal(); + + return ( + <> + {isOpen && } + {isAuthOpen && } + + ); +}; + +export default ModalProvider; \ No newline at end of file diff --git a/src/components/Activities/Attendance.jsx b/src/components/Activities/Attendance.jsx index 6d751b12..a4443dc9 100644 --- a/src/components/Activities/Attendance.jsx +++ b/src/components/Activities/Attendance.jsx @@ -12,22 +12,19 @@ import { useQueryClient } from "@tanstack/react-query"; import eventBus from "../../services/eventBus"; import { useSelectedProject } from "../../slices/apiDataManager"; -const Attendance = ({ getRole, handleModalData, searchTerm }) => { +const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizationId, includeInactive, date }) => { const queryClient = useQueryClient(); const [loading, setLoading] = useState(false); const navigate = useNavigate(); const [todayDate, setTodayDate] = useState(new Date()); const [ShowPending, setShowPending] = useState(false); - // const selectedProject = useSelector( - // (store) => store.localVariables.projectId - // ); const selectedProject = useSelectedProject(); const { attendance, loading: attLoading, recall: attrecall, isFetching - } = useAttendance(selectedProject); + } = useAttendance(selectedProject, organizationId, includeInactive, date); const filteredAttendance = ShowPending ? attendance?.filter( (att) => att?.checkInTime !== null && att?.checkOutTime === null @@ -62,12 +59,11 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => { const role = item.jobRoleName?.toLowerCase() || ""; return ( fullName.includes(lowercasedSearchTerm) || - role.includes(lowercasedSearchTerm) // ✅ also search by role + role.includes(lowercasedSearchTerm) // also search by role ); }); }, [group1, group2, searchTerm]); - const { currentPage, totalPages, currentItems, paginate } = usePagination( finalFilteredData, ITEMS_PER_PAGE @@ -116,7 +112,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => { <>
Date : {formatUTCToLocalTime(todayDate)} @@ -142,6 +138,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => { Name Role + Organization Check-In @@ -190,6 +187,8 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => { {item.jobRoleName} + {item.organizationName || "--"} + {item.checkInTime ? convertShortTime(item.checkInTime) @@ -213,7 +212,11 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => { ))} {!attendance && ( - + No employees assigned to the project! @@ -221,6 +224,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => { + {!loading && finalFilteredData.length > ITEMS_PER_PAGE && (
-
- refetch()} - /> -
+
{isLoading ? ( @@ -265,9 +285,9 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => { Name Date + Organization - {" "} - Check-In + Check-In Check-Out @@ -294,7 +314,7 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => { key={`header-${currentDate}`} className="table-row-header" > - + {moment(currentDate).format("DD-MM-YYYY")} @@ -324,6 +344,7 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => { attendance.checkInTime || attendance.checkOutTime ).format("DD-MMM-YYYY")} + {attendance.organizationName || "--"} {convertShortTime(attendance.checkInTime)} {attendance.checkOutTime @@ -345,7 +366,7 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => { ) : ( -
No Record Available !
+
No data available for the selected date range. Please Select another date.
)}
{paginatedAttendances?.length == 0 && filteredSearchData?.length > 0 && ( diff --git a/src/components/Activities/CheckCheckOutForm.jsx b/src/components/Activities/CheckCheckOutForm.jsx index ca47dd8c..40eb0a75 100644 --- a/src/components/Activities/CheckCheckOutForm.jsx +++ b/src/components/Activities/CheckCheckOutForm.jsx @@ -5,7 +5,6 @@ import { zodResolver } from "@hookform/resolvers/zod"; import TimePicker from "../common/TimePicker"; import { usePositionTracker } from "../../hooks/usePositionTracker"; import { useDispatch, useSelector } from "react-redux"; -import { markAttendance } from "../../slices/apiSlice/attedanceLogsSlice"; import showToast from "../../services/toastService"; import { checkIfCurrentDate } from "../../utils/dateUtils"; import { useMarkAttendance } from "../../hooks/useAttendance"; diff --git a/src/components/Activities/InfraPlanning.jsx b/src/components/Activities/InfraPlanning.jsx index dc237c63..0e9c9590 100644 --- a/src/components/Activities/InfraPlanning.jsx +++ b/src/components/Activities/InfraPlanning.jsx @@ -55,7 +55,7 @@ const InfraPlanning = () => { if (isFetched && (!projectInfra || projectInfra.length === 0)) { return ( -
+

No Result Found

); @@ -63,11 +63,9 @@ const InfraPlanning = () => { return (
-
-
diff --git a/src/components/Activities/Regularization.jsx b/src/components/Activities/Regularization.jsx index 82c906b5..8f1d21d5 100644 --- a/src/components/Activities/Regularization.jsx +++ b/src/components/Activities/Regularization.jsx @@ -10,13 +10,13 @@ import eventBus from "../../services/eventBus"; import { cacheData, clearCacheKey, useSelectedProject } from "../../slices/apiDataManager"; import { useQueryClient } from "@tanstack/react-query"; -const Regularization = ({ handleRequest, searchTerm }) => { +const Regularization = ({ handleRequest, searchTerm,projectId, organizationId, IncludeInActive }) => { const queryClient = useQueryClient(); // var selectedProject = useSelector((store) => store.localVariables.projectId); const selectedProject = useSelectedProject(); const [regularizesList, setregularizedList] = useState([]); const { regularizes, loading, error, refetch } = - useRegularizationRequests(selectedProject); + useRegularizationRequests(selectedProject, organizationId, IncludeInActive); useEffect(() => { setregularizedList(regularizes); @@ -59,6 +59,36 @@ const Regularization = ({ handleRequest, searchTerm }) => { }); }, [regularizesList, searchTerm]); + // const filteredSearchData = useMemo(() => { + // let sortedList = [...regularizesList].sort(sortByName); + + // // Search filter + // if (searchTerm) { + // const lowercasedSearchTerm = searchTerm.toLowerCase(); + // sortedList = sortedList.filter((item) => { + // const fullName = `${item.firstName} ${item.lastName}`.toLowerCase(); + // return fullName.includes(lowercasedSearchTerm); + // }); + // } + + // // Organization filter + // if (filters?.selectedOrganization) { + // sortedList = sortedList.filter( + // (item) => item.organization?.name === filters.selectedOrganization + // ); + // } + + // // Services filter + // if (filters?.selectedServices?.length > 0) { + // sortedList = sortedList.filter((item) => + // filters.selectedServices.includes(item.service?.name) + // ); + // } + + // return sortedList; + // }, [regularizesList, searchTerm, filters]); + + const { currentPage, totalPages, currentItems, paginate } = usePagination(filteredSearchData, 20); @@ -98,6 +128,7 @@ const Regularization = ({ handleRequest, searchTerm }) => { Name Date + Organization Check-In @@ -115,7 +146,7 @@ const Regularization = ({ handleRequest, searchTerm }) => { + />
@@ -126,6 +157,9 @@ const Regularization = ({ handleRequest, searchTerm }) => {
{moment(att.checkOutTime).format("DD-MMM-YYYY")} + + {att.organizationName || "--"} + {convertShortTime(att.checkInTime)} {att.checkOutTime ? convertShortTime(att.checkOutTime) : "--"} @@ -136,12 +170,12 @@ const Regularization = ({ handleRequest, searchTerm }) => { handleRequest={handleRequest} refresh={refetch} /> - {/*
*/} ))} + ) : (
{ const { projects } = useProjects(); - const today = new Date().toISOString().split("T")[0]; // Format: YYYY-MM-DD + const today = new Date().toISOString().split("T")[0]; // YYYY-MM-DD const [selectedDate, setSelectedDate] = useState(today); - const storedProjectId = localStorage.getItem(LOCAL_STORAGE_PROJECT_KEY); - const initialProjectId = storedProjectId || "all"; - const [selectedProjectId, setSelectedProjectId] = useState(initialProjectId); - const [displayedProjectName, setDisplayedProjectName] = - useState("Select Project"); - const [activeTab, setActiveTab] = useState("Summary"); + + // central project selection hook +const selectedProjectId = useSelectedProject() const { dashboard_Attendancedata: AttendanceData, @@ -23,38 +18,24 @@ const Attendance = () => { error: isError, } = useDashboard_AttendanceData(selectedDate, selectedProjectId); - useEffect(() => { - if (selectedProjectId === "all") { - setDisplayedProjectName("All Projects"); - } else if (projects) { - const foundProject = projects.find((p) => p.id === selectedProjectId); - setDisplayedProjectName( - foundProject ? foundProject.name : "Select Project" - ); - } else { - setDisplayedProjectName("Select Project"); - } - - localStorage.setItem(LOCAL_STORAGE_PROJECT_KEY, selectedProjectId); + // project name derived once + const displayedProjectName = useMemo(() => { + if (selectedProjectId === "all") return "All Projects"; + const found = projects?.find((p) => p.id === selectedProjectId); + return found?.name || "Select Project"; }, [selectedProjectId, projects]); - const handleProjectSelect = (projectId) => { - setSelectedProjectId(projectId); - }; - - const handleDateChange = (e) => { - setSelectedDate(e.target.value); - }; - return (
-
-
+ {/* Header */} +
+
Attendance

Daily Attendance Data

+ {/* Project Dropdown */}
@@ -77,7 +58,7 @@ const Attendance = () => {
  • @@ -88,52 +69,43 @@ const Attendance = () => {
  • -
    - {/* Tabs */} -
    -
      -
    • - -
    • -
    • - -
    • -
    -
    - {/* ✅ Date Picker Aligned Left with Padding */} -
    -
    - -
    + {/* Tabs + Date Picker */} +
    +
      +
    • + +
    • +
    • + +
    • +
    +
    + setSelectedDate(e.target.value)} + />
    + {/* Body */}
    - {activeTab === "Summary" && ( + {/* Summary */} + {AttendanceData?.activeTab === "Summary" && (
    {isLoading ? ( @@ -143,7 +115,7 @@ const Attendance = () => { ) : ( AttendanceData && ( <> -
    +
    Attendance

    @@ -164,11 +136,9 @@ const Attendance = () => {

    )} - {activeTab === "Details" && ( -
    + {/* Details */} + {AttendanceData?.activeTab === "Details" && ( +
    @@ -178,32 +148,17 @@ const Attendance = () => { - {AttendanceData?.attendanceTable && - AttendanceData.attendanceTable.length > 0 ? ( - AttendanceData.attendanceTable.map((record, index) => ( - - - - + {AttendanceData?.attendanceTable?.length ? ( + AttendanceData.attendanceTable.map((r, i) => ( + + + + )) ) : ( - + )} diff --git a/src/components/Dashboard/AttendanceChart.jsx b/src/components/Dashboard/AttendanceChart.jsx index 3879c0e4..67950d9a 100644 --- a/src/components/Dashboard/AttendanceChart.jsx +++ b/src/components/Dashboard/AttendanceChart.jsx @@ -132,7 +132,7 @@ const AttendanceOverview = () => { onClick={() => setView("table")} title="Table View" > - + diff --git a/src/components/Dashboard/Projects.jsx b/src/components/Dashboard/Projects.jsx index 0be20321..86958aa5 100644 --- a/src/components/Dashboard/Projects.jsx +++ b/src/components/Dashboard/Projects.jsx @@ -1,33 +1,28 @@ -import React, { useCallback, useEffect, useState } from "react"; +import React, { useEffect } 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); + const { + data: projectsCardData, + isLoading, + isError, + error, + refetch, + } = useDashboardProjectsCardData(); useEffect(() => { - setProjectsData(projectsCardData); - }, [projectsCardData]); + // When "project" event happens, just refetch + const handler = () => { + refetch(); + }; - 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]); + }, [refetch]); + + const totalProjects = projectsCardData?.totalProjects ?? 0; + const ongoingProjects = projectsCardData?.ongoingProjects ?? 0; return (
    @@ -37,20 +32,29 @@ const Projects = () => { Projects
    -
    -
    -

    - {projectData.totalProjects?.toLocaleString()} -

    - Total + + {isLoading ? ( +
    +
    + Loading... +
    -
    -

    - {projectData.ongoingProjects?.toLocaleString()} -

    - Ongoing + ) : isError ? ( +
    + {error?.message || "Error loading data"}
    -
    + ) : ( +
    +
    +

    {totalProjects.toLocaleString()}

    + Total +
    +
    +

    {ongoingProjects.toLocaleString()}

    + Ongoing +
    +
    + )}
    ); }; diff --git a/src/components/Dashboard/Tasks.jsx b/src/components/Dashboard/Tasks.jsx index 2513e061..56eb00f0 100644 --- a/src/components/Dashboard/Tasks.jsx +++ b/src/components/Dashboard/Tasks.jsx @@ -1,10 +1,16 @@ import React from "react"; -import { useSelector } from "react-redux"; +import { useSelectedProject } from "../../slices/apiDataManager"; import { useDashboardTasksCardData } from "../../hooks/useDashboard_Data"; const TasksCard = () => { - const projectId = useSelector((store) => store.localVariables?.projectId); - const { tasksCardData, loading, error } = useDashboardTasksCardData(projectId); + const projectId = useSelectedProject(); + + const { + data: tasksCardData, + isLoading, + isError, + error, + } = useDashboardTasksCardData(projectId); return (
    @@ -14,28 +20,30 @@ const TasksCard = () => {
    - {loading ? ( - // Loader will be displayed when loading is true + {isLoading ? ( + // Loader while fetching
    Loading...
    - ) : error ? ( - // Error message if there's an error -
    {error}
    + ) : isError ? ( + // Show error +
    + {error?.message || "Error loading data"} +
    ) : ( - // Actual data when loaded successfully + // Show data

    - {tasksCardData?.totalTasks?.toLocaleString()} + {tasksCardData?.totalTasks?.toLocaleString() ?? 0}

    Total

    - {tasksCardData?.completedTasks?.toLocaleString()} + {tasksCardData?.completedTasks?.toLocaleString() ?? 0}

    Completed
    @@ -45,4 +53,4 @@ const TasksCard = () => { ); }; -export default TasksCard; \ No newline at end of file +export default TasksCard; diff --git a/src/components/Dashboard/Teams.jsx b/src/components/Dashboard/Teams.jsx index 00e881f5..9e9d31f9 100644 --- a/src/components/Dashboard/Teams.jsx +++ b/src/components/Dashboard/Teams.jsx @@ -1,33 +1,45 @@ -import React, { useCallback, useEffect, useState } from "react"; +import React, { useCallback, useEffect } from "react"; import { useSelector } from "react-redux"; import { useDashboardTeamsCardData } from "../../hooks/useDashboard_Data"; import eventBus from "../../services/eventBus"; +import { useQueryClient } from "@tanstack/react-query"; +import { useSelectedProject } from "../../slices/apiDataManager"; const Teams = () => { - const projectId = useSelector((store) => store.localVariables?.projectId); - const { teamsCardData, loading, error } = useDashboardTeamsCardData(projectId); + const queryClient = useQueryClient(); + const projectId = useSelectedProject() - const [totalEmployees, setTotalEmployee] = useState(0); - const [inToday, setInToday] = useState(0); - - // Update state when API data arrives - useEffect(() => { - setTotalEmployee(teamsCardData?.totalEmployees || 0); - setInToday(teamsCardData?.inToday || 0); - }, [teamsCardData]); + const { + data: teamsCardData, + isLoading, + isError, + error, + } = useDashboardTeamsCardData(projectId); // Handle real-time updates via eventBus - const handler = useCallback((msg) => { - if (msg.activity === 1) { - setInToday((prev) => prev + 1); - } - }, []); + const handler = useCallback( + (msg) => { + if (msg.activity === 1) { + queryClient.setQueryData(["dashboardTeams", projectId], (old) => { + if (!old) return old; + return { + ...old, + inToday: (old.inToday || 0) + 1, + }; + }); + } + }, + [queryClient, projectId] + ); useEffect(() => { eventBus.on("attendance", handler); return () => eventBus.off("attendance", handler); }, [handler]); + const inToday = teamsCardData?.inToday ?? 0; + const totalEmployees = teamsCardData?.totalEmployees ?? 0; + return (
    @@ -36,18 +48,17 @@ const Teams = () => {
    - {loading ? ( - // Blue spinner loader + {isLoading ? (
    Loading...
    - ) : error ? ( - // Error message if data fetching fails -
    {error}
    + ) : isError ? ( +
    + {error?.message || "Error loading data"} +
    ) : ( - // Display data once loaded

    {totalEmployees.toLocaleString()}

    @@ -63,4 +74,4 @@ const Teams = () => { ); }; -export default Teams; \ No newline at end of file +export default Teams; diff --git a/src/components/Directory/ListViewContact.jsx b/src/components/Directory/ListViewContact.jsx index ef484c31..16e737a7 100644 --- a/src/components/Directory/ListViewContact.jsx +++ b/src/components/Directory/ListViewContact.jsx @@ -105,7 +105,7 @@ const ListViewContact = ({ data, Pagination }) => {
    - {record.firstName} {record.lastName} - - {new Date(record.inTime).toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit", - })} - - {new Date(record.outTime).toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit", - })} -
    {r.firstName} {r.lastName}{r.inTime ? new Date(r.inTime).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "-"}{r.outTime ? new Date(r.outTime).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "-"}
    - No attendance data available - No attendance data available
    - + {contactList?.map((col) => (
    {col.label} diff --git a/src/components/Employee/EmpAttendance.jsx b/src/components/Employee/EmpAttendance.jsx index c441b826..548471d8 100644 --- a/src/components/Employee/EmpAttendance.jsx +++ b/src/components/Employee/EmpAttendance.jsx @@ -2,7 +2,6 @@ import React, { useState, useEffect } from "react"; import moment from "moment"; import DateRangePicker, { DateRangePicker1 } from "../common/DateRangePicker"; import { useDispatch, useSelector } from "react-redux"; -import { fetchEmployeeAttendanceData } from "../../slices/apiSlice/employeeAttendanceSlice"; import usePagination from "../../hooks/usePagination"; import Avatar from "../common/Avatar"; import { convertShortTime } from "../../utils/dateUtils"; diff --git a/src/components/Layout/Header.jsx b/src/components/Layout/Header.jsx index 94ec39ac..41c7ce03 100644 --- a/src/components/Layout/Header.jsx +++ b/src/components/Layout/Header.jsx @@ -19,6 +19,7 @@ import { useProjectName } from "../../hooks/useProjects"; import eventBus from "../../services/eventBus"; import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { MANAGE_PROJECT } from "../../utils/constants"; +import { useAuthModal, useLogout } from "../../hooks/useAuth"; const Header = () => { const { profile } = useProfile(); @@ -26,10 +27,9 @@ const Header = () => { const dispatch = useDispatch(); const { data, loading } = useMaster(); const navigate = useNavigate(); + const {onOpen} = useAuthModal() const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT); - // { - // console.log(location.pathname); - // } + const { mutate : logout,isPending:logouting} = useLogout() const isDashboardPath = /^\/dashboard$/.test(location.pathname) || /^\/$/.test(location.pathname); @@ -59,41 +59,9 @@ const Header = () => { return role ? role.name : "User"; }; - const handleLogout = (e) => { - e.preventDefault(); - logout(); - }; + - const logout = async () => { - try { - let data = { - refreshToken: localStorage.getItem("refreshToken"), - }; - - AuthRepository.logout(data) - .then(() => { - localStorage.removeItem("jwtToken"); - localStorage.removeItem("refreshToken"); - localStorage.removeItem("user"); - localStorage.clear(); - clearAllCache(); - window.location.href = "/auth/login"; - }) - .catch(() => { - localStorage.removeItem("jwtToken"); - localStorage.removeItem("refreshToken"); - localStorage.removeItem("user"); - localStorage.clear(); - clearAllCache(); - window.location.href = "/auth/login"; - }); - } catch (error) { - console.error( - "Error during logout:", - error?.response?.data || error.message - ); - } - }; + const handleProfilePage = () => { navigate(`/employee/${profile?.employeeInfo?.id}`); @@ -455,6 +423,16 @@ const Header = () => { Change Password + +
  • onOpen()}> + {" "} + + + Switch Tenant + +
  • @@ -462,11 +440,10 @@ const Header = () => { handleLogout()} > - - Log Out + {logouting ? "Please Wait":<> + SignOut} diff --git a/src/components/Organization/AssignOrg.jsx b/src/components/Organization/AssignOrg.jsx new file mode 100644 index 00000000..270fac0d --- /dev/null +++ b/src/components/Organization/AssignOrg.jsx @@ -0,0 +1,260 @@ +import React, { useMemo, useState } from "react"; +import { useProjectAssignedServices } from "../../hooks/useProjects"; +import { useSelectedProject } from "../../slices/apiDataManager"; +import { + useOrganizationType, + useServices, +} from "../../hooks/masterHook/useMaster"; +import Label from "../common/Label"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { assignedOrgToProject } from "./OrganizationSchema"; +import { + useAssignOrgToProject, + useAssignOrgToTenant, + useOrganizationModal, +} from "../../hooks/useOrganization"; + +const AssignOrg = ({ setStep }) => { + const { isOpen, orgData, startStep, onOpen, flowType, prevStep, onClose } = + useOrganizationModal(); + const selectedProject = useSelectedProject(); + const { data: masterService, isLoading: isMasterserviceLoading } = + useServices(); + const { data: projectServices, isLoading } = + useProjectAssignedServices(selectedProject); + const { data: orgType, isLoading: orgLoading } = useOrganizationType(); + + const { mutate: AssignToProject, isPending: isPendingProject } = + useAssignOrgToProject(() => onClose()); + const { mutate: AssignToTenant, isPending: isPendingTenat } = + useAssignOrgToTenant(() => { + onClose(); + }); + const isPending = isPendingProject || isPendingTenat; + const mergedServices = useMemo(() => { + if (!masterService || !projectServices) return []; + const combined = [...masterService?.data, ...projectServices]; + return combined.filter( + (item, index, self) => index === self.findIndex((s) => s.id === item.id) + ); + }, [masterService, projectServices]); + + const resolver = + flowType === "default" ? undefined : zodResolver(assignedOrgToProject); + + const { + register, + handleSubmit, + setValue, + formState: { errors }, + } = useForm({ + resolver, + defaultValues: { + organizationTypeId: "", + serviceIds: [], + }, + }); + + const onSubmit = (formData) => { + if (flowType === "default") { + const payload = orgData.id; + + AssignToTenant(payload); + } else { + const payload = { + ...formData, + projectId: selectedProject, + organizationId: orgData.id, + parentOrganizationId: null, + }; + AssignToProject(payload); + } + }; + + const handleEdit = () => { + onOpen({ startStep: 4, orgData }); + }; + + const handleBack = () => { + if (prevStep === 1 && flowType === "assign") { + onOpen({ startStep: prevStep }); + } else if (prevStep === 1 && flowType !== "assign") { + onOpen({ startStep: 1 }); + } else { + onOpen({ startStep: 2 }); + } + }; + + if (isMasterserviceLoading || isLoading) + return
    Loading....
    ; + + return ( +
    + {/* Organization Info Display */} +
    +
    +
    {orgData.name}
    +
    + +
    +
    +
    + + {/* Contact Info */} +
    +
    + +
    {orgData.contactPerson}
    +
    +
    +
    +
    + +
    {orgData.contactNumber}
    +
    +
    +
    +
    + +
    {orgData.email}
    +
    +
    +
    +
    + +
    {orgData.sprid}
    +
    +
    +
    +
    + +
    {orgData.address}
    +
    +
    + + {/* Form */} +
    +
    + {/* Show fields only if flowType is NOT default */} + {flowType !== "default" && ( + <> + {/* Organization Type */} +
    + +
    + {orgType?.data.map((type) => ( +
    + + +
    + ))} +
    + {errors.organizationTypeId && ( + + {errors.organizationTypeId.message} + + )} +
    + + {/* Services */} +
    + + {mergedServices?.map((service) => ( +
    + + +
    + ))} + {errors.serviceIds && ( +
    + {errors.serviceIds.message} +
    + )} +
    + + )} + + {/* Buttons: Always visible */} +
    + + +
    +
    +
    +
    + ); +}; + +export default AssignOrg; diff --git a/src/components/Organization/ManagOrg.jsx b/src/components/Organization/ManagOrg.jsx new file mode 100644 index 00000000..0f4aa49e --- /dev/null +++ b/src/components/Organization/ManagOrg.jsx @@ -0,0 +1,203 @@ +import React, { useEffect } from "react"; +import { FormProvider, useForm } from "react-hook-form"; +import { + useCreateOrganization, + useOrganizationModal, + useUpdateOrganization, +} from "../../hooks/useOrganization"; +import { + defaultOrganizationValues, + organizationSchema, +} from "./OrganizationSchema"; +import Label from "../common/Label"; +import { useGlobalServices } from "../../hooks/masterHook/useMaster"; +import { zodResolver } from "@hookform/resolvers/zod"; +import SelectMultiple from "../common/SelectMultiple"; + +const ManagOrg = () => { + const { data: service, isLoading } = useGlobalServices(); + const { flowType, orgData, startStep, onOpen, onClose, prevStep } = + useOrganizationModal(); + + const method = useForm({ + resolver: zodResolver(organizationSchema), + defaultValues: defaultOrganizationValues, + }); + + const { + handleSubmit, + register, + reset, + formState: { errors }, + } = method; + + // Create & Update mutations + const { mutate: createOrganization, isPending: isCreating } = + useCreateOrganization(() => { + reset(defaultOrganizationValues); + onOpen({ startStep: 1 }); + onClose(); + }); + + const { mutate: updateOrganization, isPending: isUpdating } = + useUpdateOrganization(() => { + reset(defaultOrganizationValues); + onOpen({ startStep: 1 }); + onClose(); + }); + + // Prefill form if editing + useEffect(() => { + if (orgData) { + console.log(orgData); + reset({ + name: orgData.name || "", + contactPerson: orgData.contactPerson || "", + contactNumber: orgData.contactNumber || "", + email: orgData.email || "", + serviceIds: orgData.services?.map((s) => s.id) || [], + address: orgData.address || "", + }); + } + }, [orgData, reset]); + + const onSubmit = (payload) => { + if (orgData?.id) { + updateOrganization({ id: orgData.id, ...payload }); + } else { + createOrganization(payload); + } + }; + const handleBack = () => { + if (flowType === "edit") { + onClose(); + return; + } + + if (flowType === "assign") { + if (prevStep === 1) { + onOpen({ startStep: 1 }); + } else { + onOpen({ startStep: prevStep ?? 2 }); + } + return; + } + onOpen({ startStep: 2 }); + }; + return ( + +
    +
    + + + {errors.name && ( + {errors.name.message} + )} +
    + +
    + + + {errors.contactPerson && ( + {errors.contactPerson.message} + )} +
    + +
    + + + {errors.contactNumber && ( + {errors.contactNumber.message} + )} +
    + +
    + + + {errors.email && ( + {errors.email.message} + )} +
    + +
    + + {errors.serviceIds && ( + {errors.serviceIds.message} + )} +
    + +
    + + + {errors.description && ( +

    {errors.description.message}

    + )} +
    + +
    + + +
    +
    + ); +}; + +export default ManageServices; diff --git a/src/components/master/Services/ServicesSchema.js b/src/components/master/Services/ServicesSchema.js new file mode 100644 index 00000000..ceaa8bf5 --- /dev/null +++ b/src/components/master/Services/ServicesSchema.js @@ -0,0 +1,9 @@ +import { z } from "zod"; + +const schema = z.object({ + name: z.string().min(1, { message: "Service Name is required" }), + description: z + .string() + .min(1, { message: "Description is required" }) + .max(255, { message: "Description cannot exceed 255 characters" }), +}); \ No newline at end of file diff --git a/src/hooks/masterHook/useMaster.js b/src/hooks/masterHook/useMaster.js index cae0f027..6e589efe 100644 --- a/src/hooks/masterHook/useMaster.js +++ b/src/hooks/masterHook/useMaster.js @@ -9,7 +9,18 @@ import showToast from "../../services/toastService"; - +export const useServices = ()=>{ + return useQuery({ + queryKey:["services"], + queryFn:async()=> await MasterRespository.getMasterServices() + }) +} +export const useGlobalServices = ()=>{ + return useQuery({ + queryKey:["globalServices"], + queryFn:async()=> await MasterRespository.getGlobalServices() + }) +} export const useMasterMenu = ()=>{ return useQuery({ @@ -221,6 +232,12 @@ const { return { DocumentCategories, isError, isLoading, error }; } +export const useOrganizationType =()=>{ + return useQuery({ + queryKey:["orgType"], + queryFn:async()=>await MasterRespository.getOrganizationType() + }) +} // ===Application Masters Query================================================= const fetchMasterData = async (masterType) => { @@ -231,6 +248,8 @@ const fetchMasterData = async (masterType) => { return (await MasterRespository.getJobRole()).data; case "Activity": return (await MasterRespository.getActivites()).data; + case "Services": + return (await MasterRespository.getService()).data; case "Work Category": return (await MasterRespository.getWorkCategory()).data; case "Contact Category": @@ -667,6 +686,7 @@ export const useCreatePaymentMode = (onSuccessCallback)=>{ } }) } + export const useUpdatePaymentMode = (onSuccessCallback)=>{ const queryClient = useQueryClient(); @@ -689,6 +709,81 @@ export const useUpdatePaymentMode = (onSuccessCallback)=>{ }) } + +// Services------------------------------- + +// export const useCreateService = (onSuccessCallback) => { +// const queryClient = useQueryClient(); + +// return useMutation({ +// mutationFn: async (payload) => { +// const resp = await MasterRespository.createService(payload); +// return resp.data; // full API response +// }, +// onSuccess: (data) => { +// // Invalidate & refetch service list +// queryClient.invalidateQueries({ queryKey: ["masterData", "Services"] }); + +// showToast(data?.message || "Service added successfully", "success"); + +// if (onSuccessCallback) onSuccessCallback(data?.data); // pass back new service object +// }, +// onError: (error) => { +// showToast(error.message || "Something went wrong", "error"); +// }, +// }); +// }; + +export const useCreateService = (onSuccessCallback) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (payload) => { + debugger; + const resp = await MasterRespository.createService(payload); + debugger; + return resp.data; + }, + onSuccess: (data) => { + debugger; + queryClient.invalidateQueries({ queryKey: ["masterData", "Services"] }); + + showToast(data?.message || "Service added successfully", "success"); + + if (onSuccessCallback) onSuccessCallback(data?.data); + }, + onError: (error) => { + debugger; + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; + + +export const useUpdateService = (onSuccessCallback) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async ({ id, payload }) => { + const response = await MasterRespository.updateService(id, payload); + return response; // full response since it already has { success, message, data } + }, + onSuccess: (data, variables) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Services"], + }); + + showToast(data.message || "Service updated successfully.", "success"); + + if (onSuccessCallback) onSuccessCallback(data); + }, + onError: (error) => { + showToast(error?.message || "Something went wrong", "error"); + }, + }); +}; + + // -------------------Expense Status---------------------------------- export const useCreateExpenseStatus =(onSuccessCallback)=>{ const queryClient = useQueryClient(); diff --git a/src/hooks/useAttendance.js b/src/hooks/useAttendance.js index a7cdb7ac..a334c0c6 100644 --- a/src/hooks/useAttendance.js +++ b/src/hooks/useAttendance.js @@ -12,46 +12,76 @@ import { setDefaultDateRange } from "../slices/localVariablesSlice"; // ----------------------------Query----------------------------- -export const useAttendance = (projectId) => { - const dispatch = useDispatch() +// export const useAttendance = (projectId) => { +// const dispatch = useDispatch() +// const { +// data: attendance = [], +// isLoading: loading, +// error, +// refetch: recall, +// isFetching +// } = useQuery({ +// queryKey: ["attendance", projectId], +// queryFn: async () => { +// const response = await AttendanceRepository.getAttendance(projectId); +// return response.data; +// }, +// enabled: !!projectId, +// onError: (error) => { +// showToast(error.message || "Error while fetching Attendance", "error"); +// }, +// }); + +// return { +// attendance, +// loading, +// error, +// recall, +// isFetching +// }; +// }; + +export const useAttendance = (projectId, organizationId, includeInactive = false, date = null) => { + const dispatch = useDispatch(); + const { data: attendance = [], isLoading: loading, error, refetch: recall, - isFetching + isFetching, } = useQuery({ - queryKey: ["attendance", projectId], + queryKey: ["attendance", projectId, organizationId, includeInactive, date], // include filters in cache key queryFn: async () => { - const response = await AttendanceRepository.getAttendance(projectId); + const response = await AttendanceRepository.getAttendance( + projectId, + organizationId, + includeInactive, + date + ); return response.data; }, - enabled: !!projectId, + enabled: !!projectId, // only run if projectId exists onError: (error) => { showToast(error.message || "Error while fetching Attendance", "error"); }, }); - return { - attendance, - loading, - error, - recall, - isFetching - }; + return { attendance, loading, error, recall, isFetching }; }; -export const useAttendancesLogs = (projectId, fromDate, toDate) => { +export const useAttendancesLogs = (projectId, fromDate, toDate,organizationId) => { const dispatch = useDispatch(); const enabled = !!projectId && !!fromDate && !!toDate; const query = useQuery({ - queryKey: ['attendanceLogs', projectId, fromDate, toDate], + queryKey: ['attendanceLogs', projectId, fromDate, toDate,organizationId], queryFn: async () => { const res = await AttendanceRepository.getAttendanceFilteredByDate( projectId, fromDate, - toDate + toDate, + organizationId ); return res.data; }, @@ -112,30 +142,58 @@ export const useAttendanceByEmployee = (employeeId, fromDate, toDate) => { }); }; -export const useRegularizationRequests = (projectId) => { +// export const useRegularizationRequests = (projectId) => { +// const { +// data: regularizes = [], +// isLoading: loading, +// error, +// refetch, +// } = useQuery({ +// queryKey: ["regularizedList", projectId], +// queryFn: async () => { +// const response = await AttendanceRepository.getRegularizeList(projectId); +// return response.data; +// }, +// enabled: !!projectId, +// onError: (error) => { +// showToast(error.message || "Error while fetching Regularization Requests", "error"); +// }, +// }); + +// return { +// regularizes, +// loading, +// error, +// refetch, +// }; +// }; + +export const useRegularizationRequests = (projectId, organizationId, IncludeInActive = false) => { + const dispatch = useDispatch(); + const { data: regularizes = [], isLoading: loading, error, - refetch, + refetch: recall, + isFetching, } = useQuery({ - queryKey: ["regularizedList", projectId], + queryKey: ["regularizedList", projectId, organizationId, IncludeInActive], // include filters in cache key queryFn: async () => { - const response = await AttendanceRepository.getRegularizeList(projectId); + const response = await AttendanceRepository.getRegularizeList( + projectId, + organizationId, + IncludeInActive, + ); return response.data; }, - enabled: !!projectId, + enabled: !!projectId, // only run if projectId exists onError: (error) => { - showToast(error.message || "Error while fetching Regularization Requests", "error"); + showToast(error.message || "Error while fetching regularizes", "error"); }, }); - return { - regularizes, - loading, - error, - refetch, - }; + return { regularizes, loading, error, recall, isFetching }; }; diff --git a/src/hooks/useAuth.jsx b/src/hooks/useAuth.jsx new file mode 100644 index 00000000..8f158197 --- /dev/null +++ b/src/hooks/useAuth.jsx @@ -0,0 +1,83 @@ +import { useState, useEffect, useCallback } from "react"; +import { + Mutation, + useMutation, + useQuery, + useQueryClient, +} from "@tanstack/react-query"; +import { Link, useNavigate } from "react-router-dom"; +import AuthRepository from "../repositories/AuthRepository.jsx"; +import { useDispatch, useSelector } from "react-redux"; +import { + closeAuthModal, + openAuthModal, +} from "../slices/localVariablesSlice.jsx"; + +export const useTenants = () => { + return useQuery({ + queryKey: ["tenantlist"], + queryFn: async () => await AuthRepository.getTenantList(), + }); +}; + +export const useSelectTenant = (onSuccessCallBack) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (tenantId) => { + const res = await AuthRepository.selectTenant(tenantId); + return res.data; + }, + + onSuccess: (data) => { + localStorage.setItem("jwtToken", data.token); + localStorage.setItem("refreshToken", data.refreshToken); + if (onSuccessCallBack) onSuccessCallBack(); + }, + + onError: (error) => { + showToast(error.message || "Error while creating project", "error"); + localStorage.removeItem("jwtToken"); + localStorage.removeItem("refreshToken") + localStorage.removeItem("ctnt") + }, + }); +}; + +export const useAuthModal = () => { + const dispatch = useDispatch(); + const { isOpen } = useSelector((state) => state.localVariables.AuthModal); + + return { + isOpen, + onOpen: () => dispatch(openAuthModal()), + onClose: () => dispatch(closeAuthModal()), + }; +}; + +export const useLogout = ()=>{ + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async () => { + let payload = {refreshToken: localStorage.getItem("refreshToken")} + return await AuthRepository.logout(payload); + }, + + onSuccess: (data) => { + localStorage.removeItem("jwtToken"); + localStorage.removeItem("refreshToken"); + localStorage.removeItem("ctnt"); + localStorage.clear(); + window.location.href = "/auth/login"; + if (onSuccessCallBack) onSuccessCallBack(); + }, + + onError: (error) => { + showToast(error.message || "Error while creating project", "error"); + localStorage.removeItem("jwtToken"); + localStorage.removeItem("refreshToken") + localStorage.removeItem("ctnt") + }, + }); +} \ No newline at end of file diff --git a/src/hooks/useDashboard_Data.jsx b/src/hooks/useDashboard_Data.jsx index 0322566a..b9c393b7 100644 --- a/src/hooks/useDashboard_Data.jsx +++ b/src/hooks/useDashboard_Data.jsx @@ -1,7 +1,8 @@ import { useState, useEffect } from "react"; import GlobalRepository from "../repositories/GlobalRepository"; +import { useQuery } from "@tanstack/react-query"; + -// 🔹 Dashboard Progression Data Hook export const useDashboard_Data = ({ days, FromDate, projectId }) => { const [dashboard_data, setDashboard_Data] = useState([]); const [isLineChartLoading, setLoading] = useState(false); @@ -38,120 +39,120 @@ export const useDashboard_Data = ({ days, FromDate, projectId }) => { }; -export const useDashboard_AttendanceData = (date, projectId) => { - const [dashboard_Attendancedata, setDashboard_AttendanceData] = useState([]); - const [isLineChartLoading, setLoading] = useState(false); - const [error, setError] = useState(""); +// export const useDashboard_AttendanceData = (date, projectId) => { +// const [dashboard_Attendancedata, setDashboard_AttendanceData] = useState([]); +// const [isLineChartLoading, setLoading] = useState(false); +// const [error, setError] = useState(""); - useEffect(() => { - const fetchData = async () => { - setLoading(true); - setError(""); +// useEffect(() => { +// const fetchData = async () => { +// setLoading(true); +// setError(""); - try { - const response = await GlobalRepository.getDashboardAttendanceData(date, projectId); // date in 2nd param - setDashboard_AttendanceData(response.data); - } catch (err) { - setError("Failed to fetch dashboard data."); - console.error(err); - } finally { - setLoading(false); - } - }; +// try { +// const response = await GlobalRepository.getDashboardAttendanceData(date, projectId); // date in 2nd param +// setDashboard_AttendanceData(response.data); +// } catch (err) { +// setError("Failed to fetch dashboard data."); +// console.error(err); +// } finally { +// setLoading(false); +// } +// }; - if (date && projectId !== null) { - fetchData(); - } - }, [date, projectId]); +// if (date && projectId !== null) { +// fetchData(); +// } +// }, [date, projectId]); - return { dashboard_Attendancedata, isLineChartLoading: isLineChartLoading, error }; -}; +// return { dashboard_Attendancedata, isLineChartLoading: isLineChartLoading, error }; +// }; // 🔹 Dashboard Projects Card Data Hook -export const useDashboardProjectsCardData = () => { - const [projectsCardData, setProjectsData] = useState([]); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(""); +// export const useDashboardProjectsCardData = () => { +// const [projectsCardData, setProjectsData] = useState([]); +// const [loading, setLoading] = useState(false); +// const [error, setError] = useState(""); - useEffect(() => { - const fetchProjectsData = async () => { - setLoading(true); - setError(""); +// useEffect(() => { +// const fetchProjectsData = async () => { +// setLoading(true); +// setError(""); - try { - const response = await GlobalRepository.getDashboardProjectsCardData(); - setProjectsData(response.data); - } catch (err) { - setError("Failed to fetch projects card data."); - console.error(err); - } finally { - setLoading(false); - } - }; +// try { +// const response = await GlobalRepository.getDashboardProjectsCardData(); +// setProjectsData(response.data); +// } catch (err) { +// setError("Failed to fetch projects card data."); +// console.error(err); +// } finally { +// setLoading(false); +// } +// }; - fetchProjectsData(); - }, []); +// fetchProjectsData(); +// }, []); - return { projectsCardData, loading, error }; -}; +// return { projectsCardData, loading, error }; +// }; // 🔹 Dashboard Teams Card Data Hook -export const useDashboardTeamsCardData = (projectId) => { - const [teamsCardData, setTeamsData] = useState({}); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(""); +// export const useDashboardTeamsCardData = (projectId) => { +// const [teamsCardData, setTeamsData] = useState({}); +// const [loading, setLoading] = useState(false); +// const [error, setError] = useState(""); - useEffect(() => { - const fetchTeamsData = async () => { - setLoading(true); - setError(""); +// useEffect(() => { +// const fetchTeamsData = async () => { +// setLoading(true); +// setError(""); - try { - const response = await GlobalRepository.getDashboardTeamsCardData(projectId); - setTeamsData(response.data || {}); - } catch (err) { - setError("Failed to fetch teams card data."); - console.error("Error fetching teams card data:", err); - setTeamsData({}); - } finally { - setLoading(false); - } - }; +// try { +// const response = await GlobalRepository.getDashboardTeamsCardData(projectId); +// setTeamsData(response.data || {}); +// } catch (err) { +// setError("Failed to fetch teams card data."); +// console.error("Error fetching teams card data:", err); +// setTeamsData({}); +// } finally { +// setLoading(false); +// } +// }; - fetchTeamsData(); - }, [projectId]); +// fetchTeamsData(); +// }, [projectId]); - return { teamsCardData, loading, error }; -}; +// return { teamsCardData, loading, error }; +// }; -export const useDashboardTasksCardData = (projectId) => { - const [tasksCardData, setTasksData] = useState({}); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(""); +// export const useDashboardTasksCardData = (projectId) => { +// const [tasksCardData, setTasksData] = useState({}); +// const [loading, setLoading] = useState(false); +// const [error, setError] = useState(""); - useEffect(() => { - const fetchTasksData = async () => { - setLoading(true); - setError(""); +// useEffect(() => { +// const fetchTasksData = async () => { +// setLoading(true); +// setError(""); - try { - const response = await GlobalRepository.getDashboardTasksCardData(projectId); - setTasksData(response.data); - } catch (err) { - setError("Failed to fetch tasks card data."); - console.error(err); - setTasksData({}); - } finally { - setLoading(false); - } - }; +// try { +// const response = await GlobalRepository.getDashboardTasksCardData(projectId); +// setTasksData(response.data); +// } catch (err) { +// setError("Failed to fetch tasks card data."); +// console.error(err); +// setTasksData({}); +// } finally { +// setLoading(false); +// } +// }; - fetchTasksData(); - }, [projectId]); +// fetchTasksData(); +// }, [projectId]); - return { tasksCardData, loading, error }; -}; +// return { tasksCardData, loading, error }; +// }; export const useAttendanceOverviewData = (projectId, days) => { @@ -180,3 +181,75 @@ export const useAttendanceOverviewData = (projectId, days) => { return { attendanceOverviewData, loading, error }; }; + + +// -------------------Query---------------------------- + +// export const useDashboard_Data = (days, FromDate, projectId)=>{ +// return useQuery({ +// queryKey:["dashboardProjectProgress"], +// queryFn:async()=> { +// const payload = { +// days, +// FromDate: FromDate || '', +// projectId: projectId || null, +// }; +// const resp = await GlobalRepository.getDashboardProgressionData(payload); +// return resp.data; +// } +// }) +// } + +export const useDashboard_AttendanceData = (date,projectId)=>{ + return useQuery({ + queryKey:["dashboardAttendances",date,projectId], + queryFn:async()=> { + + const resp = await await GlobalRepository.getDashboardAttendanceData(date, projectId) + return resp.data; + } + }) +} + +export const useDashboardTeamsCardData =(projectId)=>{ + return useQuery({ + queryKey:["dashboardTeams",projectId], + queryFn:async()=> { + + const resp = await GlobalRepository.getDashboardTeamsCardData(projectId) + return resp.data; + } + }) +} + +export const useDashboardTasksCardData = (projectId) => { + return useQuery({ + queryKey:["dashboardTasks",projectId], + queryFn:async()=> { + + const resp = await GlobalRepository.getDashboardTasksCardData(projectId) + return resp.data; + } + }) +} +// export const useAttendanceOverviewData = (projectId, days) => { +// return useQuery({ +// queryKey:["dashboardAttendanceOverView",projectId], +// queryFn:async()=> { + +// const resp = await GlobalRepository.getAttendanceOverview(projectId, days); +// return resp.data; +// } +// }) +// } + +export const useDashboardProjectsCardData = () => { + return useQuery({ + queryKey:["dashboardProjects"], + queryFn:async()=> { + + const resp = await GlobalRepository.getDashboardProjectsCardData(); + return resp.data; + } + }) +} \ No newline at end of file diff --git a/src/hooks/useOrganization.js b/src/hooks/useOrganization.js new file mode 100644 index 00000000..2d77438b --- /dev/null +++ b/src/hooks/useOrganization.js @@ -0,0 +1,161 @@ +import { useSelector, useDispatch } from "react-redux"; +import { + toggleOrgModal, + openOrgModal, + closeOrgModal, +} from "../slices/localVariablesSlice"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import OrganizationRepository from "../repositories/OrganizationRespository"; +import showToast from "../services/toastService"; + +export const useOrganizationModal = () => { + const dispatch = useDispatch(); + const { isOpen, orgData, startStep, prevStep, flowType } = useSelector( + (state) => state.localVariables.OrganizationModal + ); + + return { + isOpen, + orgData, + startStep, + prevStep, + flowType, + onOpen: (options = {}) => + dispatch( + openOrgModal({ + isOpen: true, + orgData: options.orgData ?? orgData ?? null, + startStep: options.startStep ?? startStep ?? 1, + prevStep: options.prevStep ?? prevStep ?? 1, + flowType: options.flowType ?? flowType ?? "default", + }) + ), + onClose: () => dispatch(closeOrgModal()), + onToggle: () => dispatch(toggleOrgModal()), + }; +}; + +export const useOrganizationBySPRID = (sprid) => { + return useQuery({ + queryKey: ["organization by", sprid], + queryFn: async () => { + const resp = await OrganizationRepository.getOrganizationBySPRID(sprid); + return resp.data; + }, + enabled: !!sprid, + }); +}; + +export const useOrganizationsList = ( + pageSize, + pageNumber, + active, + sprid, + searchString = "" +) => { + return useQuery({ + queryKey: [ + "organizationList", + pageSize, + pageNumber, + active, + sprid, + searchString, + ], + queryFn: async () => { + const resp = await OrganizationRepository.getOrganizationList( + pageSize, + pageNumber, + active, + sprid, + searchString + ); + return resp.data; + }, + keepPreviousData: true, + }); +}; + +export const useCreateOrganization = (onSuccessCallback) => { + const useClient = useQueryClient(); + return useMutation({ + mutationFn: async (OrgPayload) => + await OrganizationRepository.createOrganization(OrgPayload), + onSuccess: (_, variables) => { + useClient.invalidateQueries({ queryKey: ["organizationList"] }); + showToast("Organization created successfully", "success"); + if (onSuccessCallback) onSuccessCallback(); + }, + onError: (error) => { + showToast( + error.response.data.message || + error.message || + "Something went wrong please try again !", + "error" + ); + }, + }); +}; + +export const useAssignOrgToProject = (onSuccessCallback) => { + const useClient = useQueryClient(); + return useMutation({ + mutationFn: async (payload) => + await OrganizationRepository.assignOrganizationToProject(payload), + onSuccess: (_, variables) => { + useClient.invalidateQueries({ + queryKey: ["projectAssignedOrganiztions"], + }); + showToast("Organization successfully", "success"); + if (onSuccessCallback) onSuccessCallback(); + }, + onError: (error) => { + showToast( + error.response.data.message || + error.message || + "Something went wrong please try again !", + "error" + ); + }, + }); +}; +export const useAssignOrgToTenant = (onSuccessCallback) => { + const useClient = useQueryClient(); + return useMutation({ + mutationFn: async (payload) => + await OrganizationRepository.assignOrganizationToTenanat(payload), + onSuccess: (_, variables) => { + useClient.invalidateQueries({ queryKey: ["organizationList"] }); + showToast("Organization added successfully", "success"); + if (onSuccessCallback) onSuccessCallback(); + }, + onError: (error) => { + showToast( + error.response.data.message || + error.message || + "Something went wrong please try again !", + "error" + ); + }, + }); +}; +export const useUpdateOrganization = () => { + const useClient = useQueryClient(); + return useMutation({ + mutationFn: async (payload) => + await OrganizationRepository.assignOrganizationToProject(payload), + onSuccess: (_, variables) => { + // useClient.invalidateQueries({ queryKey: ["organizationList"] }); + showToast("Organization successfully", "success"); + if (onSuccessCallback) onSuccessCallback(); + }, + onError: (error) => { + showToast( + error.response.data.message || + error.message || + "Something went wrong please try again !", + "error" + ); + }, + }); +}; diff --git a/src/hooks/useProjects.js b/src/hooks/useProjects.js index 5650f0fa..2c7e0f61 100644 --- a/src/hooks/useProjects.js +++ b/src/hooks/useProjects.js @@ -41,22 +41,30 @@ export const useProjects = () => { }; }; -export const useEmployeesByProjectAllocated = (selectedProject) => { +export const useEmployeesByProjectAllocated = ( + projectId, + serviceId, + organizationId, +) => { const { data = [], isLoading, refetch, error, } = useQuery({ - queryKey: ["empListByProjectAllocated", selectedProject], + queryKey: ["empListByProjectAllocated", projectId, serviceId,organizationId], queryFn: async () => { - const res = await ProjectRepository.getProjectAllocation(selectedProject); - return res.data || res; + const res = await ProjectRepository.getProjectAllocation( + projectId, + organizationId, + serviceId + ); + return res?.data || res; }, - enabled: !!selectedProject, + enabled: !!projectId, onError: (error) => { showToast( - error.message || "Error while Fetching project Allocated Employees", + error.message || "Error while fetching project allocated employees", "error" ); }, @@ -177,7 +185,7 @@ export const useProjectInfra = (projectId) => { data: projectInfra, isLoading, error, - isFetched + isFetched, } = useQuery({ queryKey: ["ProjectInfra", projectId], queryFn: async () => { @@ -191,26 +199,23 @@ export const useProjectInfra = (projectId) => { }, }); - return { projectInfra, isLoading, error,isFetched }; + return { projectInfra, isLoading, error, isFetched }; }; -export const useProjectTasks = (workAreaId, IsExpandedArea = false) => { - const { - data: ProjectTaskList, - isLoading, - error, - } = useQuery({ - queryKey: ["WorkItems", workAreaId], +export const useProjectTasks = (workAreaId, serviceId = null, isExpandedArea = false) => { + const { data, isLoading, error } = useQuery({ + queryKey: ["WorkItems", workAreaId, serviceId], queryFn: async () => { - const res = await ProjectRepository.getProjectTasksByWorkArea(workAreaId); - return res.data; + const res = await ProjectRepository.getProjectTasksByWorkArea(workAreaId, serviceId); + return res.data; // return actual task list }, - enabled: !!workAreaId && !!IsExpandedArea, - onError: (error) => { - showToast(error.message || "Error while Fetching project Tasks", "error"); + enabled: !!workAreaId && isExpandedArea, // only fetch if workAreaId exists and area is expanded + onError: (err) => { + showToast(err.message || "Error while fetching project tasks", "error"); }, }); - return { ProjectTaskList, isLoading, error }; + + return { ProjectTaskList: data, isLoading, error }; }; export const useProjectTasksByEmployee = (employeeId, fromDate, toDate) => { @@ -268,6 +273,31 @@ export const useProjectLevelEmployeePermission = (employeeId, projectId) => { }); }; +export const useProjectAssignedOrganizations = (projectId) => { + return useQuery({ + queryKey: ["projectAssignedOrganiztions", projectId], + queryFn: async () => { + const resp = await ProjectRepository.getProjectAssignedOrganizations( + projectId + ); + return resp.data; + }, + enabled: !!projectId, + }); +}; +export const useProjectAssignedServices = (projectId) => { + return useQuery({ + queryKey: ["projectAssignedOrganization", projectId], + queryFn: async () => { + const resp = await ProjectRepository.getProjectAssignedServices( + projectId + ); + return resp.data; + }, + enabled: !!projectId, + }); +}; + // -- -------------Mutation------------------------------- export const useCreateProject = ({ onSuccessCallback }) => { diff --git a/src/main.tsx b/src/main.tsx index 5997bb8d..d4eb60a1 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,11 +2,9 @@ import { createRoot } from 'react-dom/client' import './index.css' import App from './App.tsx' -// import { MasterDataProvider } from "./provider/MasterDataContext"; import { Provider } from 'react-redux'; import { store } from './store/store'; -import { ModalProvider } from './ModalContext.jsx'; import { ChangePasswordProvider } from './components/Context/ChangePasswordContext.jsx'; import { ModalProvider1 } from './components/ImageGallery/ModalContext.jsx'; @@ -15,15 +13,12 @@ createRoot(document.getElementById('root')!).render( // // - - - // // , ) diff --git a/src/pages/Activities/AttendancePage.jsx b/src/pages/Activities/AttendancePage.jsx index f0e00ad5..4cd5956a 100644 --- a/src/pages/Activities/AttendancePage.jsx +++ b/src/pages/Activities/AttendancePage.jsx @@ -13,11 +13,10 @@ import Regularization from "../../components/Activities/Regularization"; import { useAttendance } from "../../hooks/useAttendance"; import { useDispatch, useSelector } from "react-redux"; import { setProjectId } from "../../slices/localVariablesSlice"; -import { hasUserPermission } from "../../utils/authUtils"; import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { REGULARIZE_ATTENDANCE } from "../../utils/constants"; import eventBus from "../../services/eventBus"; -import { useProjectName } from "../../hooks/useProjects"; +import { useProjectAssignedOrganizations, useProjectName } from "../../hooks/useProjects"; import GlobalModel from "../../components/common/GlobalModel"; import CheckCheckOutmodel from "../../components/Activities/CheckCheckOutForm"; import AttendLogs from "../../components/Activities/AttendLogs"; @@ -39,6 +38,13 @@ const AttendancePage = () => { const [modelConfig, setModelConfig] = useState(); const DoRegularized = useHasUserPermission(REGULARIZE_ATTENDANCE); const { projectNames, loading: projectLoading, fetchData } = useProjectName(); + const [appliedFilters, setAppliedFilters] = useState({ + selectedOrganization: "", + selectedServices: [], + }); + + const { data: organizations = [], isLoading: orgLoading } = + useProjectAssignedOrganizations(selectedProject); const [formData, setFormData] = useState({ markTime: "", @@ -95,11 +101,11 @@ const AttendancePage = () => { {(modelConfig?.action === 0 || modelConfig?.action === 1 || modelConfig?.action === 2) && ( - - )} + + )} {/* For view logs */} {modelConfig?.action === 6 && ( @@ -128,9 +134,8 @@ const AttendancePage = () => {
  • + + + + + + + + ); +}; + +export default OrganizationPage; diff --git a/src/pages/Tenant/SelfTenantDetails.jsx b/src/pages/Tenant/SelfTenantDetails.jsx index 5375bcc3..ea4b107d 100644 --- a/src/pages/Tenant/SelfTenantDetails.jsx +++ b/src/pages/Tenant/SelfTenantDetails.jsx @@ -1,16 +1,16 @@ import React, { useEffect, useMemo } from "react"; import { useProfile } from "../../hooks/useProfile"; import TenantDetails from "./TenantDetails"; -import { hasUserPermission } from "../../utils/authUtils"; import { VIEW_TENANTS } from "../../utils/constants"; import { useNavigate } from "react-router-dom"; import Loader from "../../components/common/Loader"; +import { useHasUserPermission } from "../../hooks/useHasUserPermission"; const SelfTenantDetails = () => { const { profile, loading } = useProfile(); const tenantId = profile?.employeeInfo?.tenantId; const navigate = useNavigate(); - const isSelfTenantView = hasUserPermission(VIEW_TENANTS); + const isSelfTenantView = useHasUserPermission(VIEW_TENANTS); useEffect(() => { if (!isSelfTenantView) { diff --git a/src/pages/Tenant/TenantPage.jsx b/src/pages/Tenant/TenantPage.jsx index ef33efbf..640c8fc2 100644 --- a/src/pages/Tenant/TenantPage.jsx +++ b/src/pages/Tenant/TenantPage.jsx @@ -20,7 +20,6 @@ import TenantFilterPanel from "../../components/Tenant/TenantFilterPanel"; import { useDebounce } from "../../utils/appUtils"; import { useFab } from "../../Context/FabContext"; import { setCurrentTenant } from "../../slices/globalVariablesSlice"; -import { hasUserPermission } from "../../utils/authUtils"; // ------ Schema ------- import { @@ -35,6 +34,7 @@ import { VIEW_TENANTS, } from "../../utils/constants"; import { useProfile } from "../../hooks/useProfile"; +import { useHasUserPermission } from "../../hooks/useHasUserPermission"; // ---------- Context ---------- export const TenantContext = createContext(); @@ -63,9 +63,9 @@ const TenantPage = () => { const debouncedSearch = useDebounce(searchText, 500); const { setOffcanvasContent, setShowTrigger } = useFab(); - const isSuperTenant = hasUserPermission(SUPPER_TENANT); - const canManageTenants = hasUserPermission(MANAGE_TENANTS); - const isSelfTenant = hasUserPermission(VIEW_TENANTS); + const isSuperTenant = useHasUserPermission(SUPPER_TENANT); + const canManageTenants = useHasUserPermission(MANAGE_TENANTS); + const isSelfTenant = useHasUserPermission(VIEW_TENANTS); const methods = useForm({ resolver: zodResolver(filterSchema), diff --git a/src/pages/authentication/LoginPage.jsx b/src/pages/authentication/LoginPage.jsx index 3e331376..363de91f 100644 --- a/src/pages/authentication/LoginPage.jsx +++ b/src/pages/authentication/LoginPage.jsx @@ -44,7 +44,7 @@ const LoginPage = () => { localStorage.setItem("jwtToken", response.data.token); localStorage.setItem("refreshToken", response.data.refreshToken); setLoading(false); - navigate("/dashboard"); + navigate("/auth/switch/org"); } else { await AuthRepository.sendOTP({ email: data.username }); showToast("OTP has been sent to your email.", "success"); diff --git a/src/pages/authentication/SwitchTenant.jsx b/src/pages/authentication/SwitchTenant.jsx new file mode 100644 index 00000000..b5a8652a --- /dev/null +++ b/src/pages/authentication/SwitchTenant.jsx @@ -0,0 +1,105 @@ +import React, { useState } from "react"; +import Modal from "../../components/common/Modal"; +import { useAuthModal, useSelectTenant, useTenants } from "../../hooks/useAuth"; +import { useProfile } from "../../hooks/useProfile"; +import { useQueryClient } from "@tanstack/react-query"; +import AuthRepository from "../../repositories/AuthRepository"; +import Loader from "../../components/common/Loader"; + +const SwitchTenant = () => { + const queryClient = useQueryClient(); + const { profile } = useProfile(); + const [pendingTenant, setPendingTenant] = useState(null); + const { isOpen, onClose, onOpen } = useAuthModal(); + const { data, isLoading, isError, error } = useTenants(); + const { mutate: chooseTenant, isPending } = useSelectTenant(() => { + onClose(); + queryClient.clear(); + + // 2. Force fetch profile fresh for the new tenant + queryClient.fetchQuery({ + queryKey: ["profile"], + queryFn: () => AuthRepository.profile(), + }); + window.location.reload(); + }); + const currentTenant = localStorage.getItem("ctnt"); + const handleTenantselection = (tenantId) => { + setPendingTenant(tenantId); + localStorage.setItem("ctnt", tenantId); + chooseTenant(tenantId); + }; + + + const contentBody = ( +
    +

    Switch Workplace

    +
    + {data?.data.map((tenant) => ( +
    +
    +
    + {tenant.name} +
    + +
    +

    + {tenant?.name} +

    + +
    +

    Industry:

    +

    + {tenant?.industry?.name || "Not Available"} +

    +
    + + {tenant?.description && ( +

    + {tenant?.description} +

    + )} + + +
    +
    +
    + ))} +
    +
    + ); + return :contentBody} />; +}; + +export default SwitchTenant; \ No newline at end of file diff --git a/src/pages/authentication/TenantSelectionPage.jsx b/src/pages/authentication/TenantSelectionPage.jsx new file mode 100644 index 00000000..a628e683 --- /dev/null +++ b/src/pages/authentication/TenantSelectionPage.jsx @@ -0,0 +1,138 @@ +import { useEffect, useState } from "react"; +import { useTenants, useSelectTenant, useLogout } from "../../hooks/useAuth.jsx"; +import { Link, useNavigate } from "react-router-dom"; +import Dashboard from "../../components/Dashboard/Dashboard.jsx"; +import Loader from "../../components/common/Loader.jsx"; + +const TenantSelectionPage = () => { + const [pendingTenant, setPendingTenant] = useState(null); + const navigate = useNavigate(); + + const { data, isLoading, isError, error } = useTenants(); + const { mutate: chooseTenant, isPending } = useSelectTenant(() => { + navigate("/dashboard"); + }); + const handleTenantselection = (tenantId) => { + setPendingTenant(tenantId); + localStorage.setItem("ctnt", tenantId); + chooseTenant(tenantId); + }; + + const {mutate:handleLogout,isPending:isLogouting} = useLogout(()=>{}) + + + // useEffect(() => { + // if (localStorage.getItem("ctnt")) { + // navigate("/dashboard"); + // } + // }, [navigate]); + + useEffect(() => { + if (!isLoading && data?.data?.length === 1) { + const tenant = data.data[0]; + handleTenantselection(tenant.id); + } + }, [isLoading, data]); + + if (isLoading) return ; + + if (isLoading) { + return ; + } + + if (!data?.data?.length) { + return ( +
    +

    No tenant assigned to your account.

    +
    + ); + } + + + return ( +
    + {/* Logo */} +
    + marco-logo +
    + + {/* Heading */} +
    +

    Welcome

    +

    + Please select which dashboard you want to explore!!! +

    +
    handleLogout()}> + {isLogouting ? "Please Wait...":SignOut} +
    + +
    + + {/* Card Section */} +
    + {data?.data.map((tenant) => ( +
    +
    + + {/* Image */} +
    + {tenant.name} +
    + + {/* Content */} +
    + {/* Title */} +

    + {tenant?.name} +

    + + {/* Industry */} +
    +

    Industry:

    +

    + {tenant?.industry?.name || "Not Available"} +

    +
    + + {/* Description */} + {tenant?.description && ( +

    + {tenant?.description} +

    + )} + + {/* Button */} + +
    +
    +
    + ))} +
    +
    + + ); +}; + +export default TenantSelectionPage; \ No newline at end of file diff --git a/src/pages/authentication/tempCodeRunnerFile.jsx b/src/pages/authentication/tempCodeRunnerFile.jsx new file mode 100644 index 00000000..e9f12b60 --- /dev/null +++ b/src/pages/authentication/tempCodeRunnerFile.jsx @@ -0,0 +1 @@ +login \ No newline at end of file diff --git a/src/pages/employee/EmployeeList.jsx b/src/pages/employee/EmployeeList.jsx index 648b736b..b0f10a75 100644 --- a/src/pages/employee/EmployeeList.jsx +++ b/src/pages/employee/EmployeeList.jsx @@ -11,7 +11,7 @@ import { } from "../../hooks/useEmployees"; import { useProjectName, useProjects } from "../../hooks/useProjects"; import { useProfile } from "../../hooks/useProfile"; -import { hasUserPermission } from "../../utils/authUtils"; + import { ITEMS_PER_PAGE, MANAGE_EMPLOYEES, @@ -19,7 +19,6 @@ import { VIEW_TEAM_MEMBERS, } from "../../utils/constants"; import { clearCacheKey } from "../../slices/apiDataManager"; -import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import SuspendEmp from "../../components/Employee/SuspendEmp"; // Keep if you use SuspendEmp import { exportToCSV, @@ -36,6 +35,7 @@ import { newlineChars } from "pdf-lib"; import GlobalModel from "../../components/common/GlobalModel"; import usePagination from "../../hooks/usePagination"; import { setProjectId } from "../../slices/localVariablesSlice"; +import { useHasUserPermission } from "../../hooks/useHasUserPermission"; const EmployeeList = () => { const selectedProjectId = useSelector( diff --git a/src/pages/master/MasterPage.jsx b/src/pages/master/MasterPage.jsx index 1640f2ba..b187efd1 100644 --- a/src/pages/master/MasterPage.jsx +++ b/src/pages/master/MasterPage.jsx @@ -91,7 +91,7 @@ const MasterPage = () => { {modalConfig && ( { const projectId = useSelectedProject(); @@ -96,6 +97,8 @@ const ProjectDetails = () => { return ; case "setting": return ; + case "organization": + return ; default: return ; } diff --git a/src/repositories/AttendanceRepository.jsx b/src/repositories/AttendanceRepository.jsx index 679001f8..8d2fce4e 100644 --- a/src/repositories/AttendanceRepository.jsx +++ b/src/repositories/AttendanceRepository.jsx @@ -1,13 +1,24 @@ import { api } from "../utils/axiosClient"; +const AttendanceRepository = { + markAttendance: (data) => api.post("/api/attendance/record", data), + + getAttendance: (projectId, organizationId, includeInactive,date) => { + let url = `/api/attendance/project/team?projectId=${projectId}`; -const AttendanceRepository = { - markAttendance:(data)=>api.post("/api/attendance/record",data), - getAttendance:(id)=>api.get(`api/attendance/project/team?projectId=${id}`), - getAttendanceFilteredByDate: ( projectId, fromDate, toDate ) => - { - - let url = `api/Attendance/project/log?projectId=${ projectId }` + const params = []; + if (organizationId) params.push(`organizationId=${organizationId}`); + if (includeInactive) params.push(`includeInactive=${includeInactive}`); + if (date) params.push(`date=${date}`); + + if (params.length > 0) { + url += `&${params.join("&")}`; } + + return api.get(url); + }, + + getAttendanceFilteredByDate: (projectId, fromDate, toDate,organizationId) => { + let url = `api/Attendance/project/log?projectId=${projectId}`; if (fromDate) { url += `&dateFrom=${fromDate}`; } @@ -15,27 +26,38 @@ const AttendanceRepository = { if (toDate) { url += `&dateTo=${toDate}`; } - return api.get(url) + if (organizationId) { + url += `&organizationId=${organizationId}`; + } + return api.get(url); }, - - getAttendanceLogs: ( id ) => api.get( `api/attendance/log/attendance/${ id }` ), - getRegularizeList: ( id ) => api.get( `api/attendance/regularize?projectId=${ id }` ), - - getAttendanceByEmployee: ( employeeId, fromDate, toDate ) => - { - - let url = `api/Attendance/log/employee/${ employeeId }?` - if (fromDate) { - url += `&dateFrom=${fromDate}`; - } - - if (toDate) { - url += `&dateTo=${toDate}`; - } - return api.get(url) - }, -} + getAttendanceLogs: (id) => api.get(`api/attendance/log/attendance/${id}`), + + getRegularizeList: (projectId, organizationId, IncludeInActive) => { + let url = `/api/attendance/regularize?projectId=${projectId}`; + + const params = []; + if (organizationId) params.push(`organizationId=${organizationId}`); + if (IncludeInActive) params.push(`IncludeInActive=${IncludeInActive}`); + + if (params.length > 0) { + url += `&${params.join("&")}`; } + + return api.get(url); + }, + + getAttendanceByEmployee: (employeeId, fromDate, toDate) => { + let url = `api/Attendance/log/employee/${employeeId}?`; + if (fromDate) { + url += `&dateFrom=${fromDate}`; + } + + if (toDate) { + url += `&dateTo=${toDate}`; + } + return api.get(url); + }, +}; export default AttendanceRepository; - diff --git a/src/repositories/AuthRepository.jsx b/src/repositories/AuthRepository.jsx index fba24745..cf3f6e31 100644 --- a/src/repositories/AuthRepository.jsx +++ b/src/repositories/AuthRepository.jsx @@ -2,7 +2,7 @@ import { api } from "../utils/axiosClient"; const AuthRepository = { // Public routes (no auth token required) - login: (data) => api.postPublic("/api/auth/login", data), + login: (data) => api.postPublic("/api/auth/login/v1", data), refreshToken: (data) => api.postPublic("/api/auth/refresh-token", data), forgotPassword: (data) => api.postPublic("/api/auth/forgot-password", data), resetPassword: (data) => api.postPublic("/api/auth/reset-password", data), @@ -15,7 +15,9 @@ const AuthRepository = { logout: (data) => api.post("/api/auth/logout", data), profile: () => api.get("/api/user/profile"), changepassword: (data) => api.post("/api/auth/change-password", data), - appmenu:()=>api.get('/api/appmenu/get/menu') + appmenu: () => api.get('/api/appmenu/get/menu'), + selectTenant: (tenantId) => api.post(`/api/Auth/select-tenant/${tenantId}`), + getTenantList: () => api.get("/api/Auth/get/user/tenants"), }; diff --git a/src/repositories/MastersRepository.jsx b/src/repositories/MastersRepository.jsx index a853fdbe..a69d3562 100644 --- a/src/repositories/MastersRepository.jsx +++ b/src/repositories/MastersRepository.jsx @@ -31,6 +31,13 @@ export const MasterRespository = { getActivites: () => api.get("api/master/activities"), createActivity: (data) => api.post("api/master/activity", data), + +//Services + getService: () => api.get("api/master/service/list"), + createService: (data) => api.post("api/master/service/create", data), + updateService: (id, data) => api.put(`api/master/service/edit/${id}`, data), + "Services": (id) => api.delete(`/api/master/service/delete/${id}`), + updateActivity: (id, data) => api.post(`api/master/activity/edit/${id}`, data), getIndustries: () => api.get("api/master/industries"), @@ -106,4 +113,11 @@ export const MasterRespository = { createDocumentType: (data) => api.post(`/api/Master/document-type`, data), updateDocumentType: (id, data) => api.put(`/api/Master/document-type/edit/${id}`, data), + + + + getGlobalServices:()=>api.get("/api/Master/global-service/list"), + getMasterServices:()=>api.get("/api/Master/service/list"), + + getOrganizationType:()=>api.get('/api/Master/organization-type/list') }; diff --git a/src/repositories/OrganizationRespository.jsx b/src/repositories/OrganizationRespository.jsx new file mode 100644 index 00000000..4b4ad1b0 --- /dev/null +++ b/src/repositories/OrganizationRespository.jsx @@ -0,0 +1,20 @@ +import { api } from "../utils/axiosClient"; + +const OrganizationRepository = { + createOrganization: (data) => api.post("/api/Organization/create", data), + getOrganizationList: (pageSize, pageNumber, active, sprid, searchString) => { + return api.get( + `/api/Organization/list?pageSize=${pageSize}&pageNumber=${pageNumber}&active=${active}&${ + sprid ? `sprid=${sprid}&` : "" + }searchString=${searchString}` + ); + }, + + getOrganizationBySPRID :(sprid)=>api.get(`/api/Organization/list?sprid=${sprid}`), + + assignOrganizationToProject:(data)=>api.post(`/api/Organization/assign/project`,data), + + assignOrganizationToTenanat:(organizationId)=>api.post(`/api/Organization/assign/tenant/${organizationId}`) +}; + +export default OrganizationRepository; diff --git a/src/repositories/ProjectRepository.jsx b/src/repositories/ProjectRepository.jsx index fc1c648d..8f5ece39 100644 --- a/src/repositories/ProjectRepository.jsx +++ b/src/repositories/ProjectRepository.jsx @@ -5,14 +5,25 @@ const ProjectRepository = { getProjectByprojectId: (projetid) => api.get(`/api/project/details/${projetid}`), - getProjectAllocation: (projetid) => - api.get(`api/project/allocation/${projetid}`), + getProjectAllocation: (projectId, organizationId, serviceId) => { + let url = `/api/project/allocation/${projectId}`; + + const params = []; + if (organizationId) params.push(`organizationId=${organizationId}`); + if (serviceId) params.push(`serviceId=${serviceId}`); + + if (params.length > 0) { + url += `?${params.join("&")}`; + } + + return api.get(url); + }, + getEmployeesByProject: (projectId) => api.get(`/api/Project/employees/get/${projectId}`), manageProject: (data) => api.post("/api/project", data), - // updateProject: (data) => api.post("/api/project/update", data), manageProjectAllocation: (data) => api.post("/api/project/allocation", data), @@ -32,20 +43,31 @@ const ProjectRepository = { getProjectDetails: (id) => api.get(`/api/project/details/${id}`), getProjectInfraByproject: (id) => api.get(`/api/project/infra-details/${id}`), - getProjectTasksByWorkArea: (id) => api.get(`/api/project/tasks/${id}`), + getProjectTasksByWorkArea: (workAreaId, serviceId) => { + let url = `/api/project/tasks/${workAreaId}`; + if (serviceId) { + url += `?serviceId=${serviceId}`; + } + + return api.get(url); + }, getProjectTasksByEmployee: (id, fromDate, toDate) => api.get( `/api/project/tasks-employee/${id}?fromDate=${fromDate}&toDate=${toDate}` ), + // Permission Managment for Employee at Project Level - // Permission Managment for Employee at Project Level + getProjectLevelEmployeeList: (projectId) => api.get(`/api/Project/get/proejct-level/employees/${projectId}`), + getProjectLevelModules: () => api.get(`/api/Project/get/proejct-level/modules`), + getProjectLevelEmployeePermissions: (employeeId, projectId) => api.get(`/api/Project/get/project-level-permission/employee/${employeeId}/project/${projectId}`), + updateProjectLevelEmployeePermission: (data) => api.post(`/api/Project/assign/project-level-permission`, data), + getAllProjectLevelPermission: (projectId) => api.get(`/api/Project/get/all/project-level-permission/${projectId}`), - getProjectLevelEmployeeList:(projectId)=>api.get(`/api/Project/get/proejct-level/employees/${projectId}`), - getProjectLevelModules:()=>api.get(`/api/Project/get/proejct-level/modules`), - getProjectLevelEmployeePermissions:(employeeId,projectId)=>api.get(`/api/Project/get/project-level-permission/employee/${employeeId}/project/${projectId}`), - updateProjectLevelEmployeePermission:(data)=>api.post(`/api/Project/assign/project-level-permission`,data), - getAllProjectLevelPermission:(projectId)=>api.get(`/api/Project/get/all/project-level-permission/${projectId}`) + + // Services + getProjectAssignedServices: (projectId) => api.get(`/api/Project/get/assigned/services/${projectId}`), + getProjectAssignedOrganizations: (projectId) => api.get(`/api/Project/get/assigned/organization/${projectId}`) }; export const TasksRepository = { diff --git a/src/router/AppRoutes.jsx b/src/router/AppRoutes.jsx index 616b8893..e8be179e 100644 --- a/src/router/AppRoutes.jsx +++ b/src/router/AppRoutes.jsx @@ -50,7 +50,9 @@ import MainResetPasswordPage from "../pages/authentication/MainResetPasswordPage import TenantPage from "../pages/Tenant/TenantPage"; import { Navigate } from "react-router-dom"; import CreateTenant from "../pages/Tenant/CreateTenant"; +import OrganizationPage from "../pages/Organization/OrganizationPage"; import LandingPage from "../pages/Home/LandingPage"; +import TenantSelectionPage from "../pages/authentication/TenantSelectionPage"; const router = createBrowserRouter( [ { @@ -69,6 +71,7 @@ const router = createBrowserRouter( { path: "/auth/changepassword", element: }, ], }, + { path: "/auth/switch/org", element: }, { element: , errorElement: , @@ -97,6 +100,7 @@ const router = createBrowserRouter( { path: "/tenants/new-tenant", element: }, { path: "/tenant/:tenantId", element: }, { path: "/tenant/self", element: }, + { path: "/organizations", element: }, { path: "/help/support", element: }, { path: "/help/docs", element: }, { path: "/help/connect", element: }, diff --git a/src/router/ProtectedRoute.jsx b/src/router/ProtectedRoute.jsx index a1879e85..68dd847f 100644 --- a/src/router/ProtectedRoute.jsx +++ b/src/router/ProtectedRoute.jsx @@ -8,7 +8,7 @@ const ProtectedRoute = () => { // // const isAuthenticated = true; // isTokenValid(); // return isAuthenticated ? : - + const [isAuthenticated, setIsAuthenticated] = useState(null); useEffect(() => { @@ -66,7 +66,7 @@ const attemptTokenRefresh = async (storedRefreshToken) => { localStorage.setItem("jwtToken", response.data.token); localStorage.setItem("refreshToken", response.data.refreshToken); return true; - // api + // api // .post("/api/auth/refresh-token", { // token: localStorage.getItem("jwtToken"), // refreshToken: refreshToken, diff --git a/src/slices/apiSlice/attedanceLogsSlice.js b/src/slices/apiSlice/attedanceLogsSlice.js deleted file mode 100644 index 249d5445..00000000 --- a/src/slices/apiSlice/attedanceLogsSlice.js +++ /dev/null @@ -1,93 +0,0 @@ -import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; -import AttendanceRepository from '../../repositories/AttendanceRepository'; -import {clearCacheKey} from '../apiDataManager'; - -// Fetch attendance data -export const fetchAttendanceData = createAsyncThunk( - 'attendanceLogs/fetchAttendanceData', - async ( {projectId, fromDate, toDate}, thunkAPI ) => - { - try { - const response = await AttendanceRepository.getAttendanceFilteredByDate(projectId, fromDate, toDate); - return response?.data?.filter((log) => log.checkInTime !== null && log.activity !== 0); - } catch (error) { - return thunkAPI.rejectWithValue(error.message); - } - } -); - - -export const markAttendance = createAsyncThunk( - 'attendanceLogs/markAttendance', // Updated action type prefix - async ( formData, thunkAPI ) => - - { - try { - let newRecordAttendance = { - Id: formData.id || null, - comment: formData.description, - employeeID: formData.employeeId, - projectID: formData.projectId, - date: new Date().toISOString(), - markTime: formData.markTime, - latitude: formData.latitude.toString(), - longitude: formData.longitude.toString(), - action: formData.action, - image: null, - }; - - const response = await AttendanceRepository.markAttendance( newRecordAttendance ); - return response.data; - } catch ( error ) - { - const message = error?.response?.data?.message || error.message || "Error Occured During Api Call"; - return thunkAPI.rejectWithValue(message); - } - } -); - -// Attendance Logs Slice -const attendanceLogsSlice = createSlice({ - name: 'attendanceLogs', // Updated slice name - initialState: { - data: [], - loading: false, - error: null, - }, - reducers: { - setAttendanceData: (state, action) => { - state.data = action.payload; - }, - }, - extraReducers: (builder) => { - builder - // Fetch attendance data - .addCase(fetchAttendanceData.pending, (state) => { - state.loading = true; - }) - .addCase(fetchAttendanceData.fulfilled, (state, action) => { - state.loading = false; - state.data = action.payload; - }) - .addCase(fetchAttendanceData.rejected, (state, action) => { - state.loading = false; - state.error = action.payload; - }) - - // Mark attendance - log attenace data - .addCase(markAttendance.fulfilled, (state, action) => { - const updatedRecord = action.payload; - const index = state.data.findIndex(item => item.id === updatedRecord.id); - - if (index !== -1) { - state.data[index] = { ...state.data[index], ...updatedRecord }; - } else { - state.data.push(updatedRecord); - } - }); - - }, -}); - -export const { setAttendanceData } = attendanceLogsSlice.actions; -export default attendanceLogsSlice.reducer; diff --git a/src/slices/apiSlice/attendanceAllSlice.js b/src/slices/apiSlice/attendanceAllSlice.js deleted file mode 100644 index 1e9acdd2..00000000 --- a/src/slices/apiSlice/attendanceAllSlice.js +++ /dev/null @@ -1,38 +0,0 @@ -import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; -import AttendanceRepository from '../../repositories/AttendanceRepository'; -import {clearCacheKey} from '../apiDataManager'; - -export const markCurrentAttendance = createAsyncThunk( - 'attendanceCurrentDate/markAttendance', - async ( formData, {getState, dispatch, rejectWithValue} ) => - { - - const { projectId } = getState().localVariables - try - { - - // Create the new attendance record - const newRecordAttendance = { - Id: formData.id || null, - comment: formData.description, - employeeID: formData.employeeId, - projectId: projectId, - date: new Date().toISOString(), - markTime: formData.markTime, - latitude: formData.latitude.toString(), - longitude: formData.longitude.toString(), - action: formData.action, - image: null, - }; - - const response = await AttendanceRepository.markAttendance(newRecordAttendance); - const markedAttendance = response.data - clearCacheKey("AttendanceLogs") - return markedAttendance; - - } catch (error) { - console.error('Error marking attendance:', error); - return rejectWithValue(error.message); // Reject with error message - } - } - ); \ No newline at end of file diff --git a/src/slices/apiSlice/employeeAttendanceSlice.js b/src/slices/apiSlice/employeeAttendanceSlice.js deleted file mode 100644 index 290883c7..00000000 --- a/src/slices/apiSlice/employeeAttendanceSlice.js +++ /dev/null @@ -1,56 +0,0 @@ -import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; -import AttendanceRepository from '../../repositories/AttendanceRepository'; -import { markAttendance } from './attedanceLogsSlice'; - -export const fetchEmployeeAttendanceData = createAsyncThunk( - 'employeeAttendance/fetchEmployeeAttendanceData', - async ( {employeeId, fromDate, toDate}, thunkAPI ) => - { - try { - const response = await AttendanceRepository.getAttendanceByEmployee( employeeId, fromDate, toDate ); - // return response?.data?.filter((log) => log.checkInTime !== null && log.activity !== 0); - return response.data - } catch (error) { - return thunkAPI.rejectWithValue(error.message); - } - } -); - - -const employeeAttendancesSlice = createSlice({ - name: 'employeeAttendance', // Updated slice name - initialState: { - data: [], - loading: false, - error: null, - }, - reducers: { - setEmployeeAttendanceData: (state, action) => { - state.data = action.payload; - }, - }, - extraReducers: (builder) => { - builder - // Fetch attendance data - .addCase(fetchEmployeeAttendanceData.pending, (state) => { - state.loading = true; - }) - .addCase(fetchEmployeeAttendanceData.fulfilled, (state, action) => { - state.loading = false; - state.data = action.payload; - }) - - - .addCase(fetchEmployeeAttendanceData.rejected, (state, action) => { - state.loading = false; - state.error = action.payload; - }) - - - - - }, - }); - - export const { setEmployeeAttendanceData } = employeeAttendancesSlice.actions; - export default employeeAttendancesSlice.reducer; \ No newline at end of file diff --git a/src/slices/globalVariablesSlice.jsx b/src/slices/globalVariablesSlice.jsx index 7bdadd51..8e656000 100644 --- a/src/slices/globalVariablesSlice.jsx +++ b/src/slices/globalVariablesSlice.jsx @@ -3,23 +3,23 @@ import { createSlice } from "@reduxjs/toolkit"; const globalVariablesSlice = createSlice({ name: "globalVariables", initialState: { - loginUser:null, - currentTenant:null + loginUser: null, + currentTenant: null, }, reducers: { setGlobalVariable: (state, action) => { const { key, value } = action.payload; state[key] = value; }, - setLoginUserPermmisions: ( state, action ) => - { - state.loginUser = action.payload + setLoginUserPermmisions: (state, action) => { + state.loginUser = action.payload; + }, + setCurrentTenant: (state, action) => { + state.currentTenant = action.payload; }, - setCurrentTenant:(state,action)=>{ - state.currentTenant = action.payload - } }, }); -export const { setGlobalVariable,setLoginUserPermmisions,setCurrentTenant } = globalVariablesSlice.actions; +export const { setGlobalVariable, setLoginUserPermmisions, setCurrentTenant } = + globalVariablesSlice.actions; export default globalVariablesSlice.reducer; diff --git a/src/slices/localVariablesSlice.jsx b/src/slices/localVariablesSlice.jsx index 0b0a8405..784100e0 100644 --- a/src/slices/localVariablesSlice.jsx +++ b/src/slices/localVariablesSlice.jsx @@ -3,37 +3,86 @@ import { createSlice } from "@reduxjs/toolkit"; const localVariablesSlice = createSlice({ name: "localVariables", initialState: { - selectedMaster:"Application Role", - regularizationCount:0, - defaultDateRange: { + selectedMaster: "Application Role", + regularizationCount: 0, + defaultDateRange: { startDate: null, endDate: null, }, projectId: null, - reload:false - + reload: false, + + OrganizationModal: { + isOpen: false, + orgData: null, + prevStep: null, + startStep: 1, + flowType: "default", + }, + + AuthModal: { + isOpen: false, + }, }, reducers: { changeMaster: (state, action) => { - state.selectedMaster = action.payload; + state.selectedMaster = action.payload; }, updateRegularizationCount: (state, action) => { state.regularizationCount = action.payload; }, - setProjectId: (state, action) => { - localStorage.setItem("project",null) + setProjectId: (state, action) => { + localStorage.setItem("project", null); state.projectId = action.payload; - localStorage.setItem("project",state.projectId || null) + localStorage.setItem("project", state.projectId || null); }, - refreshData: ( state, action ) => - { - state.reload = action.payload + refreshData: (state, action) => { + state.reload = action.payload; }, setDefaultDateRange: (state, action) => { state.defaultDateRange = action.payload; }, + + openOrgModal: (state, action) => { + state.OrganizationModal.isOpen = true; + state.OrganizationModal.orgData = action.payload?.orgData || null; + + if (state.OrganizationModal.startStep) { + state.OrganizationModal.prevStep = state.OrganizationModal.startStep; + } + + state.OrganizationModal.startStep = action.payload?.startStep || 1; + state.OrganizationModal.flowType = action.payload?.flowType || "default"; + }, + closeOrgModal: (state) => { + state.OrganizationModal.isOpen = false; + state.OrganizationModal.orgData = null; + state.OrganizationModal.startStep = 1; + state.OrganizationModal.prevStep = null; + }, + toggleOrgModal: (state) => { + state.OrganizationModal.isOpen = !state.OrganizationModal.isOpen; + }, + + openAuthModal: (state, action) => { + state.AuthModal.isOpen = true; + }, + closeAuthModal: (state, action) => { + state.AuthModal.isOpen = false; + }, }, }); -export const { changeMaster ,updateRegularizationCount,setProjectId,refreshData,setDefaultDateRange} = localVariablesSlice.actions; -export default localVariablesSlice.reducer; +export const { + changeMaster, + updateRegularizationCount, + setProjectId, + refreshData, + setDefaultDateRange, + openOrgModal, + closeOrgModal, + toggleOrgModal, + openAuthModal, + closeAuthModal, +} = localVariablesSlice.actions; +export default localVariablesSlice.reducer; \ No newline at end of file diff --git a/src/store/store.jsx b/src/store/store.jsx index e1d9c03a..fca9836c 100644 --- a/src/store/store.jsx +++ b/src/store/store.jsx @@ -2,15 +2,12 @@ import { configureStore } from "@reduxjs/toolkit"; import apiCacheReducer from "../slices/apiCacheSlice"; import globalVariablesReducer from "../slices/globalVariablesSlice"; import localVariableRducer from "../slices/localVariablesSlice" -import attendanceReducer from "../slices/apiSlice/attedanceLogsSlice" -import employeeAttendanceReducer from "../slices/apiSlice/employeeAttendanceSlice" export const store = configureStore({ reducer: { apiCache: apiCacheReducer, globalVariables: globalVariablesReducer, localVariables:localVariableRducer, - attendanceLogs: attendanceReducer, - employeeAttendance: employeeAttendanceReducer, + }, }); diff --git a/src/utils/authUtils.js b/src/utils/authUtils.js index 1dd5c055..e69de29b 100644 --- a/src/utils/authUtils.js +++ b/src/utils/authUtils.js @@ -1,12 +0,0 @@ -import { useProfile } from "../hooks/useProfile"; - -export const hasUserPermission = (permission) => { - const { profile } = useProfile(); - if (profile) { - if (!permission || typeof permission !== "string") { - return false; - } - return profile?.featurePermissions.includes(permission); - } - return false; -}; diff --git a/src/utils/axiosClient.jsx b/src/utils/axiosClient.jsx index 9c4bd3ae..62e9b3bb 100644 --- a/src/utils/axiosClient.jsx +++ b/src/utils/axiosClient.jsx @@ -4,7 +4,7 @@ import axiosRetry from "axios-retry"; import showToast from "../services/toastService"; import { startSignalR, stopSignalR } from "../services/signalRService"; import { BASE_URL } from "./constants"; -const base_Url = BASE_URL +const base_Url = BASE_URL; export const axiosClient = axios.create({ baseURL: base_Url, @@ -44,7 +44,10 @@ axiosClient.interceptors.response.use( const originalRequest = error.config; // Skip retry for public requests or already retried ones - if (!originalRequest && originalRequest._retry || originalRequest.authRequired === false) { + if ( + (!originalRequest && originalRequest._retry) || + originalRequest.authRequired === false + ) { return Promise.reject(error); } @@ -53,7 +56,10 @@ axiosClient.interceptors.response.use( originalRequest._toastShown = true; if (error.code === "ERR_CONNECTION_REFUSED") { - showToast("Unable to connect to the server. Please try again later.", "error"); + showToast( + "Unable to connect to the server. Please try again later.", + "error" + ); } else if (error.code === "ERR_NETWORK") { showToast("Network error. Please check your connection.", "error"); redirectToLogin(); @@ -68,7 +74,10 @@ axiosClient.interceptors.response.use( const refreshToken = localStorage.getItem("refreshToken"); - if (!refreshToken || error.response.data?.errors === "Invalid or expired refresh token.") { + if ( + !refreshToken || + error.response.data?.errors === "Invalid or expired refresh token." + ) { redirectToLogin(); return Promise.reject(error); } @@ -88,7 +97,7 @@ axiosClient.interceptors.response.use( localStorage.setItem("jwtToken", token); localStorage.setItem("refreshToken", newRefreshToken); - startSignalR() + startSignalR(); // Set Authorization header originalRequest.headers["Authorization"] = `Bearer ${token}`; return axiosClient(originalRequest); @@ -160,4 +169,4 @@ export const api = { // Redirect helper function redirectToLogin() { window.location.href = "/auth/login"; -} \ No newline at end of file +}