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/public/assets/img/orglogo.png b/public/assets/img/orglogo.png new file mode 100644 index 00000000..51ce9299 Binary files /dev/null and b/public/assets/img/orglogo.png differ diff --git a/public/assets/vendor/css/core.css b/public/assets/vendor/css/core.css index 9be75669..48163eb5 100644 --- a/public/assets/vendor/css/core.css +++ b/public/assets/vendor/css/core.css @@ -18613,6 +18613,10 @@ li:not(:first-child) .dropdown-item, min-height: 70vh !important; } +.modal-min-h{ + min-height: 60vh !important; +} + .flex-fill { flex: 1 1 auto !important; } 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..5dce91c4 100644 --- a/src/components/Activities/InfraPlanning.jsx +++ b/src/components/Activities/InfraPlanning.jsx @@ -1,4 +1,3 @@ - import React, { useState, useEffect } from "react"; import "../../components/Project/ProjectInfra.css"; import BuildingModel from "../Project/Infrastructure/BuildingModel"; @@ -8,25 +7,33 @@ import WorkAreaModel from "../Project/Infrastructure/WorkAreaModel"; import TaskModel from "../Project/Infrastructure/TaskModel"; import ProjectRepository from "../../repositories/ProjectRepository"; import Breadcrumb from "../../components/common/Breadcrumb"; -import {useProjectDetails, useProjectInfra, useProjects} from "../../hooks/useProjects"; -import {useHasUserPermission} from "../../hooks/useHasUserPermission"; -import {APPROVE_TASK, ASSIGN_REPORT_TASK, MANAGE_PROJECT_INFRA} from "../../utils/constants"; -import {useDispatch, useSelector} from "react-redux"; -import {useProfile} from "../../hooks/useProfile"; -import {refreshData, setProjectId} from "../../slices/localVariablesSlice"; +import { + useCurrentService, + useProjectDetails, + useProjectInfra, + useProjects, +} from "../../hooks/useProjects"; +import { useHasUserPermission } from "../../hooks/useHasUserPermission"; +import { + APPROVE_TASK, + ASSIGN_REPORT_TASK, + MANAGE_PROJECT_INFRA, +} from "../../utils/constants"; +import { useDispatch, useSelector } from "react-redux"; +import { useProfile } from "../../hooks/useProfile"; +import { refreshData, setProjectId } from "../../slices/localVariablesSlice"; import InfraTable from "../Project/Infrastructure/InfraTable"; import { useSelectedProject } from "../../slices/apiDataManager"; import Loader from "../common/Loader"; - - - const InfraPlanning = () => { const { profile: LoggedUser, refetch: fetchData } = useProfile(); const dispatch = useDispatch(); const selectedProject = useSelectedProject(); + const selectedService = useCurrentService(); - const { projectInfra, isLoading, isError, error, isFetched } = useProjectInfra(selectedProject); + const { projectInfra, isLoading, isError, error, isFetched } = + useProjectInfra(selectedProject, selectedService || "" ); const canManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA); const canApproveTask = useHasUserPermission(APPROVE_TASK); @@ -55,7 +62,7 @@ const InfraPlanning = () => { if (isFetched && (!projectInfra || projectInfra.length === 0)) { return ( -
+

No Result Found

); @@ -63,11 +70,9 @@ const InfraPlanning = () => { return (
-
-
-
- -
+
+
+
@@ -75,4 +80,3 @@ const InfraPlanning = () => { }; export default InfraPlanning; - 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 [resetKey, setResetKey] = useState(0); + const selectedProjec = useSelectedProject(); + const selectedService = useCurrentService(); + const { projectInfra, isLoading, error, isFetched } = useProjectInfra( + selectedProjec, + selectedService + ); + const methods = useForm({ + resolver: zodResolver(TaskReportFilterSchema), + defaultValues: TaskReportDefaultValue, + }); + + const { + register, + reset, + handleSubmit, + formState: { errors }, + } = methods; + + const onSubmit = (formData) => { + console.log(formData) + const filterPayload = { + startDate:localToUtc(formData.startDate), + endDate:localToUtc(formData.endDate) + + } + handleFilter(filterPayload); + }; + + const onClear =()=>{ + setResetKey((prev) => prev + 1); + handleFilter(TaskReportDefaultValue) + reset(TaskReportDefaultValue) + } + + return ( + +
+
+ + +
+ + {/*
+ +
+
+ +
*/} + +
+ + +
+
+
+ ); +}; + +export default TaskReportFilterPanel; diff --git a/src/components/DailyProgressRport/TaskReportList.jsx b/src/components/DailyProgressRport/TaskReportList.jsx new file mode 100644 index 00000000..27323b51 --- /dev/null +++ b/src/components/DailyProgressRport/TaskReportList.jsx @@ -0,0 +1,301 @@ +import React, { useState, useEffect, useMemo } from "react"; +import { useTaskList } from "../../hooks/useTasks"; +import { useSelectedProject } from "../../slices/apiDataManager"; +import { useProjectName } from "../../hooks/useProjects"; +import DailyProgrssReport, { + useDailyProgrssContext, +} from "../../pages/DailyProgressReport/DailyProgrssReport"; +import { useDispatch } from "react-redux"; +import { setProjectId } from "../../slices/localVariablesSlice"; +import { + APPROVE_TASK, + ASSIGN_REPORT_TASK, + ITEMS_PER_PAGE, +} from "../../utils/constants"; +import { formatNumber, formatUTCToLocalTime } from "../../utils/dateUtils"; +import { useHasUserPermission } from "../../hooks/useHasUserPermission"; +import Pagination from "../common/Pagination"; +import { TaskReportListSkeleton } from "./TaskRepprtListSkeleton"; +import HoverPopup from "../common/HoverPopup"; + +const TaskReportList = () => { + const [currentPage, setCurrentPage] = useState(1); + const [filters, setFilters] = useState({ + selectedBuilding: "", + selectedFloors: [], + selectedActivities: [], + }); + const dispatch = useDispatch(); + const ApprovedTaskRights = useHasUserPermission(APPROVE_TASK); + const ReportTaskRights = useHasUserPermission(ASSIGN_REPORT_TASK); + + const { service, openModal, closeModal,filter } = useDailyProgrssContext(); + const selectedProject = useSelectedProject(); + const { projectNames } = useProjectName(); + + const { data, isLoading, isError, error } = useTaskList( + selectedProject, + ITEMS_PER_PAGE, + currentPage, + service,filter + ); + + const ProgrssReportColumn = [ + { + key: "activity", + label: "Activity", + getValue: (task) => task.workItem.activityMaster?.activityName || "N/A", + align: "text-start", + }, + { + key: "assigned", + label: "Total Assigned", + getValue: (task) => task.plannedTask ?? "N/A", + align: "text-start", + }, + { + key: "completed", + label: "Completed", + getValue: (task) => task.completedTask ?? "N/A", + align: "text-start", + }, + { + key: "assignAt", + label: "Assign Date", + getValue: (task) => + task.assignmentDate ? formatUTCToLocalTime(task.assignmentDate) : "N/A", + align: "text-start", + }, + { + key: "team", + label: "Team", + getValue: (task) => + task.teamMembers?.map((m) => `${m.firstName} ${m.lastName}`).join(", ") || + "N/A", + align: "text-start", + }, + ]; + + const paginate = (page) => { + if (page >= 1 && page <= (data?.totalPages ?? 1)) { + setCurrentPage(page); + } + }; + + useEffect(() => { + if (!selectedProject && projectNames.length > 0) { + dispatch(setProjectId(projectNames[0].id)); + } + }, [selectedProject, projectNames, dispatch]); + + useEffect(() => { + setFilters({ + selectedBuilding: "", + selectedFloors: [], + selectedActivities: [], + }); + }, [selectedProject]); + + // Filter and Group wise data + + const filteredTasks = useMemo(() => { + if (!data?.data) return []; + return data?.data.filter((task) => { + const { selectedBuilding, selectedFloors, selectedActivities } = filters; + + if ( + selectedBuilding && + task?.workItem?.workArea?.floor?.building?.name !== selectedBuilding + ) + return false; + if ( + selectedFloors.length > 0 && + !selectedFloors.includes(task?.workItem?.workArea?.floor?.floorName) + ) + return false; + if ( + selectedActivities.length > 0 && + !selectedActivities.includes( + task?.workItem?.activityMaster?.activityName + ) + ) + return false; + + return true; + }); + }, [data?.data, filters, currentPage]); + + const groupedTasks = useMemo(() => { + const groups = {}; + filteredTasks.forEach((task) => { + const date = task.assignmentDate.split("T")[0]; + if (!groups[date]) groups[date] = []; + groups[date].push(task); + }); + return Object.keys(groups) + .sort((a, b) => new Date(b) - new Date(a)) + .map((date) => ({ date, tasks: groups[date] })); + }, [filteredTasks, paginate, currentPage, selectedProject]); + + const renderTeamMembers = (task, refIndex) => ( +
+ ${task.teamMembers + .map( + (m) => ` +
+
+ + ${m?.firstName?.charAt(0) || ""}${m?.lastName?.charAt(0) || "" + } + +
+ ${m.firstName} ${m.lastName} +
` + ) + .join("")} +
+ `} + > + {task.teamMembers.slice(0, 3).map((m) => ( +
+ + {m?.firstName.slice(0, 1)} + +
+ ))} + {task.teamMembers.length > 3 && ( +
+ + +{task.teamMembers.length - 3} + +
+ )} +
+ ); + + if (isLoading) return ; + if (isError) return
Loading....
; + return ( +
+ + + + + + + + + + + + + {groupedTasks.length === 0 && ( + + + + )} + + {groupedTasks.map(({ date, tasks }) => ( + + + + + {tasks.map((task, idx) => ( + + + + + + + + + ))} + + ))} + +
Activity + + Total Pending{" "} + This shows the total pending tasks for each activity on that date.

} + > + +
+
+
+ + Reported/Planned{" "} + This shows the reported versus planned tasks for each activity on that date.

} + > + +
+
+
Assign DateTeamActions
+ No reports available +
+ {formatUTCToLocalTime(date)} +
+
+ {task.workItem.activityMaster?.activityName || "No Activity Name"} +
+
+ {task.workItem.workArea?.floor?.building?.name} ›{" "} + {task.workItem.workArea?.floor?.floorName} ›{" "} + {task.workItem.workArea?.areaName} +
+
+ {formatNumber(task.workItem.plannedWork)} + {`${formatNumber(task.completedTask)} / ${formatNumber(task.plannedTask)}`}{formatUTCToLocalTime(task.assignmentDate)}{renderTeamMembers(task, idx)} +
+ {ReportTaskRights && !task.reportedDate && ( + + )} + {ApprovedTaskRights && task.reportedDate && !task.approvedBy && ( + + )} + +
+
+ {data?.data?.length > 0 && ( + + )} +
+ ); +}; + +export default TaskReportList; diff --git a/src/components/DailyProgressRport/TaskRepprtListSkeleton.jsx b/src/components/DailyProgressRport/TaskRepprtListSkeleton.jsx new file mode 100644 index 00000000..5580e924 --- /dev/null +++ b/src/components/DailyProgressRport/TaskRepprtListSkeleton.jsx @@ -0,0 +1,62 @@ + +const SkeletonLine = ({ height = 20, width = "100%", className = "" }) => ( +
+); +export const TaskReportListSkeleton = () => { + const skeletonRows = 8; // Number of placeholder rows + + return ( +
+ + + + + + + + + + + + + {[...Array(skeletonRows)].map((_, idx) => ( + + + + + + + + + ))} + +
ActivityAssignedCompletedAssign OnTeamActions
+ + + + + + + + + +
+ {[...Array(3)].map((_, i) => ( + + ))} +
+
+
+ + +
+
+
+ ); +}; diff --git a/src/components/DailyProgressRport/TaskRportScheam.jsx b/src/components/DailyProgressRport/TaskRportScheam.jsx new file mode 100644 index 00000000..11a79e6f --- /dev/null +++ b/src/components/DailyProgressRport/TaskRportScheam.jsx @@ -0,0 +1,15 @@ +import { z } from "zod"; + +export const TaskReportFilterSchema = z.object({ + // buildingIds: z.array(z.string()).optional(), + // floorIds: z.array(z.string()).optional(), + startDate: z.string().optional(), + endDate: z.string().optional(), +}); + +export const TaskReportDefaultValue = { + // buildingIds:[], + // floorIds:[], + startDate:null, + endDate:null +} \ No newline at end of file diff --git a/src/components/Dashboard/Activity.jsx b/src/components/Dashboard/Activity.jsx index a1f9c5fc..78483ed5 100644 --- a/src/components/Dashboard/Activity.jsx +++ b/src/components/Dashboard/Activity.jsx @@ -1,194 +1,194 @@ -import React, { useState, useEffect } from "react"; -import LineChart from "../Charts/LineChart"; -import { useProjects } from "../../hooks/useProjects"; -import { useDashboard_ActivityData } from "../../hooks/useDashboard_Data"; -import ApexChart from "../Charts/Circlechart"; +// import React, { useState, useEffect } from "react"; +// import LineChart from "../Charts/LineChart"; +// import { useProjects } from "../../hooks/useProjects"; +// import { useDashboard_ActivityData } from "../../hooks/useDashboard_Data"; +// import ApexChart from "../Charts/Circlechart"; -const LOCAL_STORAGE_PROJECT_KEY = "selectedActivityProjectId"; +// const LOCAL_STORAGE_PROJECT_KEY = "selectedActivityProjectId"; -const Activity = () => { - const { projects } = useProjects(); - const today = new Date().toISOString().split("T")[0]; // Format: 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("all"); +// const Activity = () => { +// const { projects } = useProjects(); +// const today = new Date().toISOString().split("T")[0]; // Format: 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("all"); - const { dashboard_Activitydata: ActivityData, isLoading, error: isError } = - useDashboard_ActivityData(selectedDate, selectedProjectId); +// const { dashboard_Activitydata: ActivityData, isLoading, error: isError } = +// useDashboard_ActivityData(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"); - } +// 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); - }, [selectedProjectId, projects]); +// localStorage.setItem(LOCAL_STORAGE_PROJECT_KEY, selectedProjectId); +// }, [selectedProjectId, projects]); - const handleProjectSelect = (projectId) => { - setSelectedProjectId(projectId); - }; +// const handleProjectSelect = (projectId) => { +// setSelectedProjectId(projectId); +// }; - const handleDateChange = (e) => { - setSelectedDate(e.target.value); - }; +// const handleDateChange = (e) => { +// setSelectedDate(e.target.value); +// }; - return ( -
-
-
-
-
Activity
-

Activity Progress Chart

-
+// return ( +//
+//
+//
+//
+//
Activity
+//

Activity Progress Chart

+//
-
- -
    -
  • - -
  • - {projects?.map((project) => ( -
  • - -
  • - ))} -
-
-
-
+//
+// +//
    +//
  • +// +//
  • +// {projects?.map((project) => ( +//
  • +// +//
  • +// ))} +//
+//
+//
+//
- {/* ✅ Date Picker Aligned Left with Padding */} -
-
- -
-
+// {/* ✅ Date Picker Aligned Left with Padding */} +//
+//
+// +//
+//
- {/* Tabs */} -
    -
  • - -
  • -
  • - -
  • -
+// {/* Tabs */} +//
    +//
  • +// +//
  • +//
  • +// +//
  • +//
-
- {activeTab === "all" && ( -
-
- {isLoading ? ( -

Loading activity data...

- ) : isError ? ( -

No data available.

- ) : ( - ActivityData && ( - <> -
- Allocated Task -
-

- {ActivityData.totalCompletedWork?.toLocaleString()}/ - {ActivityData.totalPlannedWork?.toLocaleString()} -

- Completed / Assigned -
- -
- - ) - )} -
+//
+// {activeTab === "all" && ( +//
+//
+// {isLoading ? ( +//

Loading activity data...

+// ) : isError ? ( +//

No data available.

+// ) : ( +// ActivityData && ( +// <> +//
+// Allocated Task +//
+//

+// {ActivityData.totalCompletedWork?.toLocaleString()}/ +// {ActivityData.totalPlannedWork?.toLocaleString()} +//

+// Completed / Assigned +//
+// +//
+// +// ) +// )} +//
-
- {!isLoading && !isError && ActivityData && ( - <> -
- Activities -
-

- {ActivityData.totalCompletedWork?.toLocaleString()}/ - {ActivityData.totalPlannedWork?.toLocaleString()} -

- Pending / Assigned -
- -
- - )} -
-
- )} +//
+// {!isLoading && !isError && ActivityData && ( +// <> +//
+// Activities +//
+//

+// {ActivityData.totalCompletedWork?.toLocaleString()}/ +// {ActivityData.totalPlannedWork?.toLocaleString()} +//

+// Pending / Assigned +//
+// +//
+// +// )} +//
+//
+// )} - {activeTab === "logs" && ( -
- - - - - - - - - {[{ - activity: "Code Review / Remote", - assignedToday: 3, - completed: 2 - }].map((log, index) => ( - - - - - ))} - -
Activity / LocationAssigned / Completed
{log.activity}{log.assignedToday} / {log.completed}
-
- )} -
-
- ); -}; +// {activeTab === "logs" && ( +//
+// +// +// +// +// +// +// +// +// {[{ +// activity: "Code Review / Remote", +// assignedToday: 3, +// completed: 2 +// }].map((log, index) => ( +// +// +// +// +// ))} +// +//
Activity / LocationAssigned / Completed
{log.activity}{log.assignedToday} / {log.completed}
+//
+// )} +//
+//
+// ); +// }; -export default Activity; +// export default Activity; diff --git a/src/components/Dashboard/Attendance.jsx b/src/components/Dashboard/Attendance.jsx index 168fb065..5d010acc 100644 --- a/src/components/Dashboard/Attendance.jsx +++ b/src/components/Dashboard/Attendance.jsx @@ -1,21 +1,16 @@ -import React, { useState, useEffect } from "react"; -import LineChart from "../Charts/LineChart"; +import React, { useState, useMemo } from "react"; +import ApexChart from "../Charts/Circle"; import { useProjects } from "../../hooks/useProjects"; import { useDashboard_AttendanceData } from "../../hooks/useDashboard_Data"; -import ApexChart from "../Charts/Circle"; - -const LOCAL_STORAGE_PROJECT_KEY = "selectedAttendanceProjectId"; +import { useSelectedProject } from "../../hooks/useSelectedProject"; // ✅ your custom hook const Attendance = () => { 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) => ( - - - - - - - - - - - - - ); -}; - -export default ProjectListView; \ No newline at end of file diff --git a/src/pages/project/ProjectPage.jsx b/src/pages/project/ProjectPage.jsx new file mode 100644 index 00000000..4fdccc30 --- /dev/null +++ b/src/pages/project/ProjectPage.jsx @@ -0,0 +1,235 @@ +import React, { createContext, useContext, useEffect, useState } from "react"; +import Breadcrumb from "../../components/common/Breadcrumb"; +import { ITEMS_PER_PAGE, MANAGE_PROJECT, PROJECT_STATUS } from "../../utils/constants"; +import ProjectListView from "../../components/Project/ProjectListView"; +import GlobalModel from "../../components/common/GlobalModel"; +import ManageProjectInfo from "../../components/Project/ManageProjectInfo"; +import ProjectCardView from "../../components/Project/ProjectCardView"; +import usePagination from "../../hooks/usePagination"; +import { useProjects } from "../../hooks/useProjects"; +import Loader from "../../components/common/Loader"; +import { useHasUserPermission } from "../../hooks/useHasUserPermission"; + +const ProjectContext = createContext(); +export const useProjectContext = () => { + const context = useContext(ProjectContext); + if (!context) { + throw new Error("useProjectContext must be used within an ProjectProvider"); + } + return context; +}; + +const ProjectPage = () => { + const [manageProject, setMangeProject] = useState({ + isOpen: false, + Project: null, + }); + + const [projectList, setProjectList] = useState([]); + const [listView, setListView] = useState(false); + const [searchTerm, setSearchTerm] = useState(""); + const HasManageProject = useHasUserPermission(MANAGE_PROJECT); + + const [selectedStatuses, setSelectedStatuses] = useState( + PROJECT_STATUS.map((s) => s.id) + ); + + const { data, isLoading, isError, error } = useProjects(); + + const contextDispatcher = { + setMangeProject, + }; + + const filteredProjects = projectList.filter((project) => { + const matchesStatus = selectedStatuses.includes(project.projectStatusId); + const matchesSearch = project.name + .toLowerCase() + .includes(searchTerm.toLowerCase()); + return matchesStatus && matchesSearch; + }); + + const totalPages = Math.ceil(filteredProjects.length / ITEMS_PER_PAGE); + + const { currentItems, currentPage, paginate, setCurrentPage } = usePagination( + filteredProjects, + ITEMS_PER_PAGE + ); + + const handleStatusChange = (statusId) => { + setCurrentPage(1); + setSelectedStatuses((prev) => + prev.includes(statusId) + ? prev.filter((id) => id !== statusId) + : [...prev, statusId] + ); + }; + + const sortingProject = (projects) => { + if (!isLoading && Array.isArray(projects)) { + const grouped = {}; + + projects.forEach((project) => { + const statusId = project.projectStatusId; + if (!grouped[statusId]) grouped[statusId] = []; + grouped[statusId].push(project); + }); + + const sortedGrouped = selectedStatuses + .filter((statusId) => grouped[statusId]) + .flatMap((statusId) => + grouped[statusId].sort((a, b) => + a.name.toLowerCase().localeCompare(b.name.toLowerCase()) + ) + ); + + setProjectList((prev) => { + const isSame = JSON.stringify(prev) === JSON.stringify(sortedGrouped); + return isSame ? prev : sortedGrouped; + }); + } + }; + + useEffect(() => { + if (!isLoading && data) { + sortingProject(data); + } + }, [data, isLoading, selectedStatuses]); + + + if(isLoading) return
    + if(isError) return

    {error.message}

    + return ( + +
    + + +
    +
    +
    +
    +
    + { + setSearchTerm(e.target.value); + setCurrentPage(1); + }} + /> +
    + +
    + + +
    + +
    + +
      + {PROJECT_STATUS.map(({ id, label }) => ( +
    • +
      + handleStatusChange(id)} + /> + +
      +
    • + ))} +
    +
    +
    + +
    + {HasManageProject && ( )} +
    +
    +
    +
    + + {/* Project Render here */} + {listView ? ( + + ) : ( + + )} + + {/* ------------------ */} + + {/* Project Manage UPdate or create */} + + {manageProject.isOpen && ( + setMangeProject({ isOpen: false, Project: null })} + > + setMangeProject({ isOpen: false, Project: null })} + /> + + )} +
    +
    + ); +}; + +export default ProjectPage; 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..700020fd 100644 --- a/src/repositories/AuthRepository.jsx +++ b/src/repositories/AuthRepository.jsx @@ -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..3e9fac8f 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,21 @@ 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"), + getActivityGrops: (serviceId) => + api.get(`/api/Master/activity-group/list?serviceId=${serviceId}`), + createActivityGroup: (data) => + api.post(`/api/Master/activity-group/create`, data), + updateActivityGrop: (serviceId, data) => + api.put(`/api/Master/activity-group/edit/${serviceId}`, data), + getActivitesByGroup: (activityGroupId) => + api.get(`api/master/activities?activityGroupId=${activityGroupId}`), + deleteActivityGroup:(id)=>api.delete(`/api/Master/activity-group/delete/${id}`), + + + deleteActivity:(id)=>api.delete(`/api/Master/activity/delete/${id}`), + + 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..722423a9 --- /dev/null +++ b/src/repositories/OrganizationRespository.jsx @@ -0,0 +1,44 @@ +import { api } from "../utils/axiosClient"; + +const OrganizationRepository = { + createOrganization: (data) => api.post("/api/Organization/create", data), + updateOrganizaion:(id,data)=>api.put(`/api/Organization/edit/${id}`,data), + getOrganizaion:(id)=>api.get(`/api/Organization/details/${id}`), + 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}`), + + getOrganizationEmployees: (projectId, organizationId, searchString) => { + let url = `/api/Employee/list/organizations/${projectId}`; + const queryParams = []; + + if (organizationId) { + queryParams.push(`organizationId=${organizationId}`); + } + + if (searchString) { + queryParams.push(`searchString=${encodeURIComponent(searchString)}`); + } + + if (queryParams.length > 0) { + url += `?${queryParams.join("&")}`; + } + + return api.get(url); + }, +}; + +export default OrganizationRepository; diff --git a/src/repositories/ProjectRepository.jsx b/src/repositories/ProjectRepository.jsx index fc1c648d..43a1ee66 100644 --- a/src/repositories/ProjectRepository.jsx +++ b/src/repositories/ProjectRepository.jsx @@ -5,14 +5,26 @@ const ProjectRepository = { getProjectByprojectId: (projetid) => api.get(`/api/project/details/${projetid}`), - getProjectAllocation: (projetid) => - api.get(`api/project/allocation/${projetid}`), + getProjectAllocation: (projectId, serviceId, organizationId, employeeStatus) => { + let url = `/api/project/allocation/${projectId}`; + + const params = []; + if (organizationId) params.push(`organizationId=${organizationId}`); + if (serviceId) params.push(`serviceId=${serviceId}`); + if (employeeStatus !== undefined) params.push(`includeInactive=${employeeStatus}`); + + 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), @@ -31,21 +43,60 @@ const ProjectRepository = { projectNameList: () => api.get("/api/project/list/basic"), 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}`), + getProjectInfraByproject: (projectId, serviceId) => { + let url = `/api/project/infra-details/${projectId}`; + + if (serviceId) { + url + `?serviceId=${serviceId}`; + } + return api.get(url); + }, + 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}`), + + getEmployeeForTaskAssign: (projectId, serviceId, organizationId) => { + let url = `/api/Project/get/task/team/${projectId}`; + + const params = []; + if (serviceId) params.push(`serviceId=${serviceId}`); + if (organizationId) params.push(`organizationId=${organizationId}`); + + if (params.length > 0) { + url += `?${params.join("&")}`; + } + return api.get(url); + }, }; export const TasksRepository = { diff --git a/src/repositories/TaskRepository.jsx b/src/repositories/TaskRepository.jsx index 40ad8750..33883452 100644 --- a/src/repositories/TaskRepository.jsx +++ b/src/repositories/TaskRepository.jsx @@ -1,25 +1,26 @@ import { api } from "../utils/axiosClient"; export const TasksRepository = { - getTaskList: (id, fromdate = null, todate = null) => { - let url = `api/task/list?projectId=${id}`; + getTaskList: (projectId, pageSize, pageNumber, serviceId, filter) => { + const payloadJsonString = JSON.stringify(filter); + let url = `api/task/list?projectId=${projectId}&pageSize=${pageSize}&pageNumber=${pageNumber}`; - if (fromdate) { - url += `&dateFrom=${fromdate}`; + if (serviceId) { + url += `&serviceId=${serviceId}`; } - - if (todate) { - url += `&dateTo=${todate}`; + if (filter && Object.keys(filter).length > 0) { + const payloadJsonString = encodeURIComponent(JSON.stringify(filter)); + url += `&filter=${payloadJsonString}`; } + debugger return api.get(url); }, - getTaskById:(id)=>api.get(`/api/task/get/${id}`), + getTaskById: (id) => api.get(`/api/task/get/${id}`), reportTask: (data) => api.post("api/task/report", data), - taskComments: ( data ) => api.post( "api/task/comment", data ), - auditTask: ( data ) => api.post( '/api/task/approve', data ), - - assignTask:(data) =>api.post('/api/task/assign',data) + taskComments: (data) => api.post("api/task/comment", data), + auditTask: (data) => api.post("/api/task/approve", data), + assignTask: (data) => api.post("/api/task/assign", data), }; diff --git a/src/router/AppRoutes.jsx b/src/router/AppRoutes.jsx index 6907ea85..823ebb3a 100644 --- a/src/router/AppRoutes.jsx +++ b/src/router/AppRoutes.jsx @@ -13,7 +13,6 @@ import ChangePasswordPage from "../pages/authentication/ChangePassword"; // Home & Protected Pages import Dashboard from "../components/Dashboard/Dashboard"; -import ProjectList from "../pages/project/ProjectList"; import ProjectDetails from "../pages/project/ProjectDetails"; import ManageProject from "../components/Project/ManageProject"; import EmployeeList from "../pages/employee/EmployeeList"; @@ -21,7 +20,6 @@ import EmployeeList from "../pages/employee/EmployeeList"; import EmployeeProfile from "../pages/employee/EmployeeProfile"; import Inventory from "../pages/project/Inventory"; import AttendancePage from "../pages/Activities/AttendancePage"; -import DailyTask from "../pages/Activities/DailyTask"; import TaskPlannng from "../pages/Activities/TaskPlannng"; import Reports from "../pages/reports/Reports"; import ImageGallary from "../pages/Gallary/ImageGallary"; @@ -49,7 +47,11 @@ 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"; +import DailyProgrssReport from "../pages/DailyProgressReport/DailyProgrssReport"; +import ProjectPage from "../pages/project/ProjectPage"; const router = createBrowserRouter( [ { @@ -68,6 +70,7 @@ const router = createBrowserRouter( { path: "/auth/changepassword", element: }, ], }, + { path: "/auth/switch/org", element: }, { element: , errorElement: , @@ -76,7 +79,7 @@ const router = createBrowserRouter( element: , children: [ { path: "/dashboard", element: }, - { path: "/projects", element: }, + { path: "/projects", element: }, { path: "/projects/details", element: }, { path: "/project/manage/:projectId", element: }, { path: "/employees", element: }, @@ -86,7 +89,7 @@ const router = createBrowserRouter( { path: "/directory", element: }, { path: "/inventory", element: }, { path: "/activities/attendance", element: }, - { path: "/activities/records/:projectId?", element: }, + { path: "/activities/records/:projectId?", element: }, { path: "/activities/task", element: }, { path: "/activities/reports", element: }, { path: "/gallary", element: }, @@ -96,6 +99,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..d174652c 100644 --- a/src/router/ProtectedRoute.jsx +++ b/src/router/ProtectedRoute.jsx @@ -2,13 +2,69 @@ import React, { useState, useEffect } from "react"; import { Navigate, Outlet } from "react-router-dom"; import { jwtDecode } from "jwt-decode"; import AuthRepository from "../repositories/AuthRepository"; +import { removeSession } from "../utils/authUtils"; + +const isTokenExpired = (token) => { + if (!token) return true; + try { + const { exp } = jwtDecode(token); + return exp * 1000 < Date.now(); + } catch { + return true; + } +}; + +const validateToken = async () => { + const token = + localStorage.getItem("jwtToken") || + sessionStorage.getItem("jwtToken"); + + const refreshTokenStored = + localStorage.getItem("refreshToken") || + sessionStorage.getItem("refreshToken"); + + if (!refreshTokenStored){ + console.log("no refrh tokem"); + removeSession() + return false + }; + + if (isTokenExpired(token)) { + return await attemptTokenRefresh(refreshTokenStored); + } + + return true; +}; + +const attemptTokenRefresh = async (storedRefreshToken) => { + try { + const currentToken = + localStorage.getItem("jwtToken") || + sessionStorage.getItem("jwtToken"); + + const response = await AuthRepository.refreshToken({ + token: currentToken, + refreshToken: storedRefreshToken, + }); + + const { token: newToken, refreshToken: newRefreshToken } = response.data; + + if (localStorage.getItem("jwtToken")) { + localStorage.setItem("jwtToken", newToken); + localStorage.setItem("refreshToken", newRefreshToken); + } else { + sessionStorage.setItem("jwtToken", newToken); + sessionStorage.setItem("refreshToken", newRefreshToken); + } + + return true; + } catch (error) { + console.error("Token refresh failed:", error); + return false; + } +}; const ProtectedRoute = () => { - // const isAuthenticated = localStorage.getItem("jwtToken"); // Example authentication check - // // const isAuthenticated = true; - // isTokenValid(); - // return isAuthenticated ? : - const [isAuthenticated, setIsAuthenticated] = useState(null); useEffect(() => { @@ -21,80 +77,10 @@ const ProtectedRoute = () => { }, []); if (isAuthenticated === null) { - return
    Loading...
    ; // Show a loader while checking + return
    Loading...
    ; } return isAuthenticated ? : ; }; -// Function to check if the token is expired -const isTokenExpired = (token) => { - if (!token) return true; - try { - const { exp } = jwtDecode(token); - return exp * 1000 < Date.now(); // Check if expired - } catch (error) { - return true; // If decoding fails, treat as expired - } -}; - -// Function to validate and refresh the token if expired -export const validateToken = async () => { - const token = localStorage.getItem("jwtToken"); - const refreshTokenStored = localStorage.getItem("refreshToken"); - // If refresh token is absent, cannot proceed - if (!refreshTokenStored) { - console.warn("No refresh token available. Redirecting to login."); - return false; - } - - // If access token expired, try to refresh - if (isTokenExpired(token)) { - return await attemptTokenRefresh(refreshTokenStored); - } - return true; -}; - -// Attempt to refresh the access token -const attemptTokenRefresh = async (storedRefreshToken) => { - try { - const response = await AuthRepository.refreshToken({ - token: localStorage.getItem("jwtToken"), - refreshToken: storedRefreshToken, - }); - - localStorage.setItem("jwtToken", response.data.token); - localStorage.setItem("refreshToken", response.data.refreshToken); - return true; - // api - // .post("/api/auth/refresh-token", { - // token: localStorage.getItem("jwtToken"), - // refreshToken: refreshToken, - // }) - // .then((data) => { - // localStorage.setItem("jwtToken", response.data.token); - // localStorage.setItem("refreshToken", response.data.refreshToken); - // return true; - // }) - // .catch((error) => { - // console.error("Token refresh failed:", error); - // }); - - // const refreshToken = localStorage.getItem("refreshToken"); - // const response = await axiosClient.post(`/api/auth/refresh-token`, { - // token: localStorage.getItem("jwtToken"), - // refreshToken: refreshToken, - // }); - - // if (response.status === 200) { - // localStorage.setItem("jwtToken", response.data.token); - // localStorage.setItem("refreshToken", response.data.refreshToken); - // return true; - // } - } catch (error) { - console.error("Token refresh failed:", error); - return false; - } -}; - export default ProtectedRoute; 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..cd861ec7 100644 --- a/src/slices/globalVariablesSlice.jsx +++ b/src/slices/globalVariablesSlice.jsx @@ -3,23 +3,27 @@ import { createSlice } from "@reduxjs/toolkit"; const globalVariablesSlice = createSlice({ name: "globalVariables", initialState: { - loginUser:null, - currentTenant:null + loginUser: null, + currentTenant: null, + selectedServiceId : 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; + }, + setService: (state, action) => { + state.selectedServiceId = action.payload; }, - setCurrentTenant:(state,action)=>{ - state.currentTenant = action.payload - } }, }); -export const { setGlobalVariable,setLoginUserPermmisions,setCurrentTenant } = globalVariablesSlice.actions; +export const { setGlobalVariable, setLoginUserPermmisions, setCurrentTenant, setService} = + 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/appUtils.js b/src/utils/appUtils.js index 21f7fa5a..6f652db9 100644 --- a/src/utils/appUtils.js +++ b/src/utils/appUtils.js @@ -1,31 +1,31 @@ import { useEffect, useState } from "react"; import { format, parseISO } from "date-fns"; -export const formatFileSize=(bytes)=> { +export const formatFileSize = (bytes) => { if (bytes < 1024) return bytes + " B"; else if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + " KB"; else return (bytes / (1024 * 1024)).toFixed(2) + " MB"; -} +}; export const AppColorconfig = { colors: { - primary: '#696cff', - secondary: '#8592a3', - success: '#71dd37', - info: '#03c3ec', - warning: '#ffab00', - danger: '#ff3e1d', - dark: '#233446', - black: '#000', - white: '#fff', - cardColor: '#fff', - bodyBg: '#f5f5f9', - bodyColor: '#697a8d', - headingColor: '#566a7f', - textMuted: '#a1acb8', - borderColor: '#eceef1' - } + primary: "#696cff", + secondary: "#8592a3", + success: "#71dd37", + info: "#03c3ec", + warning: "#ffab00", + danger: "#ff3e1d", + dark: "#233446", + black: "#000", + white: "#fff", + cardColor: "#fff", + bodyBg: "#f5f5f9", + bodyColor: "#697a8d", + headingColor: "#566a7f", + textMuted: "#a1acb8", + borderColor: "#eceef1", + }, }; export const getColorNameFromHex = (hex) => { - const normalizedHex = hex?.replace(/'/g, '').toLowerCase(); + const normalizedHex = hex?.replace(/'/g, "").toLowerCase(); const colors = AppColorconfig.colors; for (const [name, value] of Object.entries(colors)) { @@ -62,18 +62,19 @@ export const getIconByFileType = (type = "") => { return "bx bx-file"; }; - export const normalizeAllowedContentTypes = (allowedContentType) => { if (!allowedContentType) return []; if (Array.isArray(allowedContentType)) return allowedContentType; - if (typeof allowedContentType === "string") return allowedContentType.split(","); + if (typeof allowedContentType === "string") + return allowedContentType.split(","); return []; }; - export function localToUtc(localDateString) { - if (!localDateString || localDateString.trim() === "") return null; // return null instead of undefined - const date = new Date(localDateString); - if (isNaN(date.getTime())) return null; // invalid date check - return date.toISOString(); -} \ No newline at end of file + if (!localDateString || localDateString.trim() === "") return null; + + const [day, month, year] = localDateString.split("-"); + const date = new Date(`${year}-${month}-${day}T00:00:00`); + + return isNaN(date.getTime()) ? null : date.toISOString(); +} diff --git a/src/utils/authUtils.js b/src/utils/authUtils.js index 1dd5c055..8e266583 100644 --- a/src/utils/authUtils.js +++ b/src/utils/authUtils.js @@ -1,12 +1,7 @@ -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; -}; +export const removeSession = () => { + localStorage.removeItem("jwtToken"); + localStorage.removeItem("refreshToken"); + sessionStorage.removeItem("jwtToken"); + sessionStorage.removeItem("refreshToken"); + localStorage.removeItem("ctnt"); +}; \ No newline at end of file diff --git a/src/utils/axiosClient.jsx b/src/utils/axiosClient.jsx index 9c4bd3ae..bf26500e 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, @@ -16,14 +16,14 @@ export const axiosClient = axios.create({ // Auto retry failed requests (e.g., network issues) axiosRetry(axiosClient, { retries: 3 }); - // Request Interceptor — Add Bearer token if required axiosClient.interceptors.request.use( async (config) => { const requiresAuth = config.authRequired !== false; // default to true if (requiresAuth) { - const token = localStorage.getItem("jwtToken"); + const token = + localStorage.getItem("jwtToken") || sessionStorage.getItem("jwtToken"); if (token) { config.headers["Authorization"] = `Bearer ${token}`; config._retry = true; @@ -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(); @@ -66,9 +72,12 @@ axiosClient.interceptors.response.use( if (status === 401 && !isRefreshRequest) { originalRequest._retry = true; - const refreshToken = localStorage.getItem("refreshToken"); + const refreshToken = localStorage.getItem("refreshToken") || sessionStorage.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); } @@ -78,17 +87,22 @@ axiosClient.interceptors.response.use( try { // Refresh token call const res = await axiosClient.post("/api/Auth/refresh-token", { - token: localStorage.getItem("jwtToken"), + token: localStorage.getItem("jwtToken") || sessionStorage.getItem("jwtToken"), refreshToken, }); const { token, refreshToken: newRefreshToken } = res.data.data; // Save updated tokens - localStorage.setItem("jwtToken", token); - localStorage.setItem("refreshToken", newRefreshToken); + if (localStorage.getItem("jwtToken")) { + localStorage.setItem("jwtToken", token); + localStorage.setItem("refreshToken", newRefreshToken); + } else { + sessionStorage.setItem("jwtToken", token); + sessionStorage.setItem("refreshToken", newRefreshToken); + } - startSignalR() + startSignalR(); // Set Authorization header originalRequest.headers["Authorization"] = `Bearer ${token}`; return axiosClient(originalRequest); @@ -160,4 +174,4 @@ export const api = { // Redirect helper function redirectToLogin() { window.location.href = "/auth/login"; -} \ No newline at end of file +} diff --git a/src/utils/constants.jsx b/src/utils/constants.jsx index 1af801ca..62c686c9 100644 --- a/src/utils/constants.jsx +++ b/src/utils/constants.jsx @@ -1,54 +1,53 @@ -export const THRESH_HOLD = 48; // hours +export const THRESH_HOLD = 48; // hours export const DURATION_TIME = 10; // minutes export const ITEMS_PER_PAGE = 20; -export const OTP_EXPIRY_SECONDS = 300 // OTP time +export const OTP_EXPIRY_SECONDS = 300; // OTP time export const MANAGE_MASTER = "588a8824-f924-4955-82d8-fc51956cf323"; -export const VIEW_MASTER = "5ffbafe0-7ab0-48b1-bb50-c1bf76b65f9d" +export const VIEW_MASTER = "5ffbafe0-7ab0-48b1-bb50-c1bf76b65f9d"; -export const MANAGE_PROJECT = "172fc9b6-755b-4f62-ab26-55c34a330614" +export const MANAGE_PROJECT = "172fc9b6-755b-4f62-ab26-55c34a330614"; -export const VIEW_PROJECTS = "6ea44136-987e-44ba-9e5d-1cf8f5837ebc" +export const VIEW_PROJECTS = "6ea44136-987e-44ba-9e5d-1cf8f5837ebc"; -export const MANAGE_EMPLOYEES = "a97d366a-c2bb-448d-be93-402bd2324566" +export const MANAGE_EMPLOYEES = "a97d366a-c2bb-448d-be93-402bd2324566"; -export const VIEW_ALL_EMPLOYEES = "60611762-7f8a-4fb5-b53f-b1139918796b" +export const VIEW_ALL_EMPLOYEES = "60611762-7f8a-4fb5-b53f-b1139918796b"; -export const VIEW_TEAM_MEMBERS = "b82d2b7e-0d52-45f3-997b-c008ea460e7f" +export const VIEW_TEAM_MEMBERS = "b82d2b7e-0d52-45f3-997b-c008ea460e7f"; -export const MANAGE_TEAM = "b94802ce-0689-4643-9e1d-11c86950c35b" +export const MANAGE_TEAM = "b94802ce-0689-4643-9e1d-11c86950c35b"; -export const MANAGE_PROJECT_INFRA = "cf2825ad-453b-46aa-91d9-27c124d63373" +export const MANAGE_PROJECT_INFRA = "cf2825ad-453b-46aa-91d9-27c124d63373"; -export const VIEW_PROJECT_INFRA = "8d7cc6e3-9147-41f7-aaa7-fa507e450bd4" - -export const REGULARIZE_ATTENDANCE ="57802c4a-00aa-4a1f-a048-fd2f70dd44b6" -export const TEAM_ATTENDANCE = "915e6bff-65f6-4e3f-aea8-3fd217d3ea9e" -export const SELF_ATTENDANCE = "ccb0589f-712b-43de-92ed-5b6088e7dc4e" +export const VIEW_PROJECT_INFRA = "8d7cc6e3-9147-41f7-aaa7-fa507e450bd4"; +export const REGULARIZE_ATTENDANCE = "57802c4a-00aa-4a1f-a048-fd2f70dd44b6"; +export const TEAM_ATTENDANCE = "915e6bff-65f6-4e3f-aea8-3fd217d3ea9e"; +export const SELF_ATTENDANCE = "ccb0589f-712b-43de-92ed-5b6088e7dc4e"; export const ASSIGN_TO_PROJECT = "b94802ce-0689-4643-9e1d-11c86950c35b"; export const INFRASTRUCTURE = "9666de86-d7c7-4d3d-acaa-fcd6d6b81f3c"; -export const MANAGE_TASK = "08752f33-3b29-4816-b76b-ea8a968ed3c5" +export const MANAGE_TASK = "08752f33-3b29-4816-b76b-ea8a968ed3c5"; -export const APPROVE_TASK = "db4e40c5-2ba9-4b6d-b8a6-a16a250ff99c" +export const APPROVE_TASK = "db4e40c5-2ba9-4b6d-b8a6-a16a250ff99c"; -export const VIEW_TASK = "9fcc5f87-25e3-4846-90ac-67a71ab92e3c" +export const VIEW_TASK = "9fcc5f87-25e3-4846-90ac-67a71ab92e3c"; -export const ASSIGN_REPORT_TASK = "6a32379b-8b3f-49a6-8c48-4b7ac1b55dc2" +export const ASSIGN_REPORT_TASK = "6a32379b-8b3f-49a6-8c48-4b7ac1b55dc2"; // ------------------------Directory------------------------------------- -export const DIRECTORY_ADMIN = "4286a13b-bb40-4879-8c6d-18e9e393beda" +export const DIRECTORY_ADMIN = "4286a13b-bb40-4879-8c6d-18e9e393beda"; -export const DIRECTORY_MANAGER = "62668630-13ce-4f52-a0f0-db38af2230c5" +export const DIRECTORY_MANAGER = "62668630-13ce-4f52-a0f0-db38af2230c5"; -export const DIRECTORY_USER = "0f919170-92d4-4337-abd3-49b66fc871bb" +export const DIRECTORY_USER = "0f919170-92d4-4337-abd3-49b66fc871bb"; // -----------------------Expense---------------------------------------- -export const VIEW_SELF_EXPENSE = "385be49f-8fde-440e-bdbc-3dffeb8dd116" +export const VIEW_SELF_EXPENSE = "385be49f-8fde-440e-bdbc-3dffeb8dd116"; export const VIEW_ALL_EXPNESE = "01e06444-9ca7-4df4-b900-8c3fa051b92f"; @@ -58,19 +57,22 @@ export const REVIEW_EXPENSE = "1f4bda08-1873-449a-bb66-3e8222bd871b"; export const APPROVE_EXPENSE = "eaafdd76-8aac-45f9-a530-315589c6deca"; -export const PROCESS_EXPENSE = "ea5a1529-4ee8-4828-80ea-0e23c9d4dd11" +export const PROCESS_EXPENSE = "ea5a1529-4ee8-4828-80ea-0e23c9d4dd11"; -export const EXPENSE_MANAGE = "ea5a1529-4ee8-4828-80ea-0e23c9d4dd11" +export const EXPENSE_MANAGE = "ea5a1529-4ee8-4828-80ea-0e23c9d4dd11"; -export const EXPENSE_REJECTEDBY = ["d1ee5eec-24b6-4364-8673-a8f859c60729","965eda62-7907-4963-b4a1-657fb0b2724b"] +export const EXPENSE_REJECTEDBY = [ + "d1ee5eec-24b6-4364-8673-a8f859c60729", + "965eda62-7907-4963-b4a1-657fb0b2724b", +]; -export const EXPENSE_DRAFT = "297e0d8f-f668-41b5-bfea-e03b354251c8" +export const EXPENSE_DRAFT = "297e0d8f-f668-41b5-bfea-e03b354251c8"; // ----------------------------Tenant------------------------- -export const SUPPER_TENANT = "d032cb1a-3f30-462c-bef0-7ace73a71c0b" -export const MANAGE_TENANTS = "00e20637-ce8d-4417-bec4-9b31b5e65092" -export const VIEW_TENANTS = "647145c6-2108-4c98-aab4-178602236e55" -export const ActiveTenant = "297e0d8f-f668-41b5-bfea-e03b354251c8" +export const SUPPER_TENANT = "d032cb1a-3f30-462c-bef0-7ace73a71c0b"; +export const MANAGE_TENANTS = "00e20637-ce8d-4417-bec4-9b31b5e65092"; +export const VIEW_TENANTS = "647145c6-2108-4c98-aab4-178602236e55"; +export const ActiveTenant = "297e0d8f-f668-41b5-bfea-e03b354251c8"; // ---------------------Documents--------------------------------- export const VIEW_DOCUMENT = "71189504-f1c8-4ca5-8db6-810497be2854"; @@ -81,43 +83,64 @@ export const DOWNLOAD_DOCUMENT = "404373d0-860f-490e-a575-1c086ffbce1d"; export const VERIFY_DOCUMENT = "13a1f30f-38d1-41bf-8e7a-b75189aab8e0"; // -------------------Application Role------------------------------ -// 1 - Expense Manage -export const EXPENSE_MANAGEMENT = "a4e25142-449b-4334-a6e5-22f70e4732d7" +// 1 - Expense Manage +export const EXPENSE_MANAGEMENT = "a4e25142-449b-4334-a6e5-22f70e4732d7"; export const TENANT_STATUS = [ - {id:"62b05792-5115-4f99-8ff5-e8374859b191",name:"Active"}, - {id:"c0b5def8-087e-4235-b3a4-8e2f0ed91b94",name:"In Active"}, - {id:"35d7840a-164a-448b-95e6-efb2ec84a751",name:"Supspended"} -] + { id: "62b05792-5115-4f99-8ff5-e8374859b191", name: "Active" }, + { id: "c0b5def8-087e-4235-b3a4-8e2f0ed91b94", name: "In Active" }, + { id: "35d7840a-164a-448b-95e6-efb2ec84a751", name: "Supspended" }, +]; export const DOCUMENTS_ENTITIES = { - ProjectEntity : "c8fe7115-aa27-43bc-99f4-7b05fabe436e", - EmployeeEntity:"dbb9555a-7a0c-40f2-a9ed-f0463f1ceed7", -} + ProjectEntity: "c8fe7115-aa27-43bc-99f4-7b05fabe436e", + EmployeeEntity: "dbb9555a-7a0c-40f2-a9ed-f0463f1ceed7", +}; - -export const CONSTANT_TEXT = { - -} +export const CONSTANT_TEXT = {}; export const SUBSCRIPTION_PLAN_FREQUENCIES = { 0: "Monthly", - 1:"Quarterly", - 2:"Half-Yearly", - 3:"Yearly" -} + 1: "Quarterly", + 2: "Half-Yearly", + 3: "Yearly", +}; export const reference = [ - { val: "google", name: "Google" }, - { val: "frineds", name: "Friends" }, - { val: "advertisement", name: "Advertisement" }, - { val: "root tenant", name: "Root Tenant" }, - ]; -export const orgSize = [ - { val: "1-50", name: "1-50" }, - { val: "51-100", name: "51-100" }, - { val: "101-500", name: "101-500" }, - { val: "500+", name: "500+" }, + { val: "google", name: "Google" }, + { val: "frineds", name: "Friends" }, + { val: "advertisement", name: "Advertisement" }, + { val: "root tenant", name: "Root Tenant" }, ]; +export const orgSize = [ + { val: "1-50", name: "1-50" }, + { val: "51-100", name: "51-100" }, + { val: "101-500", name: "101-500" }, + { val: "500+", name: "500+" }, +]; + +export const PROJECT_STATUS = [ + { + id: "b74da4c2-d07e-46f2-9919-e75e49b12731", + label: "Active", + }, + { + id: "cdad86aa-8a56-4ff4-b633-9c629057dfef", + label: "In Progress", + }, + { + id: "603e994b-a27f-4e5d-a251-f3d69b0498ba", + label: "On Hold", + }, + { + id: "ef1c356e-0fe0-42df-a5d3-8daee355492d", + label: "Inactive", + }, + { + id: "33deaef9-9af1-4f2a-b443-681ea0d04f81", + label: "Completed", + }, +]; +export const DEFAULT_EMPTY_STATUS_ID = "00000000-0000-0000-0000-000000000000"; export const BASE_URL = process.env.VITE_BASE_URL;
    {col.label} diff --git a/src/components/Documents/DocumentFilterPanel.jsx b/src/components/Documents/DocumentFilterPanel.jsx index c305b9bb..15a2cbf1 100644 --- a/src/components/Documents/DocumentFilterPanel.jsx +++ b/src/components/Documents/DocumentFilterPanel.jsx @@ -96,7 +96,7 @@ const DocumentFilterPanel = ({ entityTypeId, onApply }) => { placeholder="DD-MM-YYYY To DD-MM-YYYY" startField="startDate" endField="endDate" - defaultRange={true} + defaultRange={false} resetSignal={resetKey} maxDate={new Date()} /> diff --git a/src/components/Documents/Documents.jsx b/src/components/Documents/Documents.jsx index b1dbe493..8210bf58 100644 --- a/src/components/Documents/Documents.jsx +++ b/src/components/Documents/Documents.jsx @@ -118,7 +118,7 @@ const Documents = ({ Document_Entity, Entity }) => { return (
    -
    +
    {/* Search */}
    @@ -149,30 +149,11 @@ const Documents = ({ Document_Entity, Entity }) => {
    - {/* Actions */}
    - {/* { - setSearchText(""); - setFilter(DocumentFilterDefaultValues); - refetchFn && refetchFn(); - }} - > - Refresh - - */} - {(isSelf || canUploadDocument) && ( )}
    diff --git a/src/components/Employee/DemoTable.jsx b/src/components/Employee/DemoTable.jsx deleted file mode 100644 index 1d645cc3..00000000 --- a/src/components/Employee/DemoTable.jsx +++ /dev/null @@ -1,172 +0,0 @@ -import React from "react"; - -const DemoTable = () => { - return ( -
    -
    -
    -
    - - - - - - - - - - - - - - -
    idNameEmailDateSalaryStatusAction
    -
    -
    -
    -
    -
    - New Record -
    - -
    -
    -
    -
    - -
    - - - - -
    -
    -
    - -
    - - - - -
    -
    -
    - -
    - - - - -
    -
    - You can use letters, numbers & periods -
    -
    -
    - -
    - - - - -
    -
    -
    - -
    - - - - -
    -
    -
    - - -
    -
    -
    -
    - -
    - -
    - -
    -
    - -
    -
    - ); -}; - -export default DemoTable; 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/Employee/EmployeeList.jsx b/src/components/Employee/EmployeeList.jsx deleted file mode 100644 index 5a79c6ce..00000000 --- a/src/components/Employee/EmployeeList.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from "react"; - -const EmployeeList = () => { - return
    EmployeeList
    ; -}; - -export default EmployeeList; \ No newline at end of file diff --git a/src/components/Employee/EmployeeSchema.jsx b/src/components/Employee/EmployeeSchema.jsx new file mode 100644 index 00000000..ba540ef4 --- /dev/null +++ b/src/components/Employee/EmployeeSchema.jsx @@ -0,0 +1,124 @@ +import { z } from "zod" + + +const mobileNumberRegex = /^[0-9]\d{9}$/; + +export const employeeSchema = + z.object({ + firstName: z.string().min(1, { message: "First Name is required" }), + middleName: z.string().optional(), + lastName: z.string().min(1, { message: "Last Name is required" }), + email: z + .string() + .max(80, "Email cannot exceed 80 characters") + .optional() + .refine((val) => !val || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val), { + message: "Invalid email format", + }) + .refine( + (val) => { + if (!val) return true; + const [local, domain] = val.split("@"); + return ( + val.length <= 320 && local?.length <= 64 && domain?.length <= 255 + ); + }, + { + message: "Email local or domain part is too long", + } + ), + currentAddress: z + .string() + .min(1, { message: "Current Address is required" }) + .max(500, { message: "Address cannot exceed 500 characters" }), + birthDate: z + .string() + .min(1, { message: "Birth Date is required" }) + .refine( + (date, ctx) => { + return new Date(date) <= new Date(); + }, + { + message: "Birth date cannot be in the future", + } + ), + joiningDate: z + .string() + .min(1, { message: "Joining Date is required" }) + .refine( + (date, ctx) => { + return new Date(date) <= new Date(); + }, + { + message: "Joining date cannot be in the future", + } + ), + emergencyPhoneNumber: z + .string() + .min(1, { message: "Phone Number is required" }) + .regex(mobileNumberRegex, { message: "Invalid phone number " }), + emergencyContactPerson: z + .string() + .min(1, { message: "Emergency Contact Person is required" }) + .regex(/^[A-Za-z\s]+$/, { + message: "Emergency Contact Person must contain only letters", + }), + aadharNumber: z + .string() + .optional() + .refine((val) => !val || /^\d{12}$/.test(val), { + message: "Aadhar card must be exactly 12 digits long", + }), + gender: z + .string() + .min(1, { message: "Gender is required" }) + .refine((val) => val !== "Select Gender", { + message: "Please select a gender", + }), + panNumber: z + .string() + .optional() + .refine((val) => !val || /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/.test(val), { + message: "Invalid PAN number", + }), + permanentAddress: z + .string() + .min(1, { message: "Permanent Address is required" }) + .max(500, { message: "Address cannot exceed 500 characters" }), + phoneNumber: z + .string() + .min(1, { message: "Phone Number is required" }) + .regex(mobileNumberRegex, { message: "Invalid phone number " }), + jobRoleId: z.string().min(1, { message: "Role is required" }), + organizationId:z.string().min(1,{message:"Organization is required"}), + hasApplicationAccess:z.boolean().default(false), + }).refine((data) => { + if (data.hasApplicationAccess) { + return data.email && data.email.trim() !== ""; + } + return true; +}, { + message: "Email is required when employee has access", + path: ["email"], +}); + + +export const defatEmployeeObj = { + firstName: "", + middleName: "", + lastName: "", + email: "", + currentAddress: "", + birthDate: "", + joiningDate: "", + emergencyPhoneNumber: "", + emergencyContactPerson: "", + aadharNumber: "", + gender: "", + panNumber: "", + permanentAddress: "", + phoneNumber: "", + jobRoleId: null, + organizationId:"", + hasApplicationAccess:false + } \ No newline at end of file diff --git a/src/components/Employee/ManageEmployee.jsx b/src/components/Employee/ManageEmployee.jsx index 81a4c4d2..f57ca994 100644 --- a/src/components/Employee/ManageEmployee.jsx +++ b/src/components/Employee/ManageEmployee.jsx @@ -1,36 +1,37 @@ import React, { useEffect, useState } from "react"; -import showToast from "../../services/toastService"; -import EmployeeRepository from "../../repositories/EmployeeRepository"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; -import { z } from "zod"; + import useMaster from "../../hooks/masterHook/useMaster"; import { useDispatch } from "react-redux"; import { changeMaster } from "../../slices/localVariablesSlice"; import { Link, useNavigate, useParams } from "react-router-dom"; import { formatDate } from "../../utils/dateUtils"; -import { useEmployeeProfile, useUpdateEmployee } from "../../hooks/useEmployees"; import { - cacheData, - clearCacheKey, - getCachedData, -} from "../../slices/apiDataManager"; -import { clearApiCacheKey } from "../../slices/apiCacheSlice"; -import { useMutation } from "@tanstack/react-query"; + useEmployeeProfile, + useUpdateEmployee, +} from "../../hooks/useEmployees"; + import Label from "../common/Label"; import DatePicker from "../common/DatePicker"; - -const mobileNumberRegex = /^[0-9]\d{9}$/; +import { defatEmployeeObj, employeeSchema } from "./EmployeeSchema"; +import { useOrganizationsList } from "../../hooks/useOrganization"; +import { ITEMS_PER_PAGE } from "../../utils/constants"; const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => { const dispatch = useDispatch(); const { mutate: updateEmployee, isPending } = useUpdateEmployee(); - + const { + data: organzationList, + isLoading, + isError, + error: EempError, + } = useOrganizationsList(ITEMS_PER_PAGE, 1, true); const { employee, error, loading: empLoading, - refetch + refetch, } = useEmployeeProfile(employeeId); useEffect(() => { @@ -38,6 +39,7 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => { }, [employeeId]); const [disabledEmail, setDisabledEmail] = useState(false); + const { data: job_role, loading } = useMaster(); const [isloading, setLoading] = useState(false); const navigation = useNavigate(); @@ -45,98 +47,9 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => { const [currentAddressLength, setCurrentAddressLength] = useState(0); const [permanentAddressLength, setPermanentAddressLength] = useState(0); - const userSchema = z.object({ - ...(employeeId ? { id: z.string().optional() } : {}), - firstName: z.string().min(1, { message: "First Name is required" }), - middleName: z.string().optional(), - lastName: z.string().min(1, { message: "Last Name is required" }), - email: z - .string() - .max(80, "Email cannot exceed 80 characters") - .optional() - .refine((val) => !val || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val), { - message: "Invalid email format", - }) - .refine( - (val) => { - if (!val) return true; - const [local, domain] = val.split("@"); - return ( - val.length <= 320 && local?.length <= 64 && domain?.length <= 255 - ); - }, - { - message: "Email local or domain part is too long", - } - ), - currentAddress: z - .string() - .min(1, { message: "Current Address is required" }) - .max(500, { message: "Address cannot exceed 500 characters" }), - birthDate: z - .string() - .min(1, { message: "Birth Date is required" }) - .refine( - (date, ctx) => { - return new Date(date) <= new Date(); - }, - { - message: "Birth date cannot be in the future", - } - ), - joiningDate: z - .string() - .min(1, { message: "Joining Date is required" }) - .refine( - (date, ctx) => { - return new Date(date) <= new Date(); - }, - { - message: "Joining date cannot be in the future", - } - ), - emergencyPhoneNumber: z - .string() - .min(1, { message: "Phone Number is required" }) - .regex(mobileNumberRegex, { message: "Invalid phone number " }), - emergencyContactPerson: z - .string() - .min(1, { message: "Emergency Contact Person is required" }) - .regex(/^[A-Za-z\s]+$/, { - message: "Emergency Contact Person must contain only letters", - }), - aadharNumber: z - .string() - .optional() - .refine((val) => !val || /^\d{12}$/.test(val), { - message: "Aadhar card must be exactly 12 digits long", - }), - gender: z - .string() - .min(1, { message: "Gender is required" }) - .refine((val) => val !== "Select Gender", { - message: "Please select a gender", - }), - panNumber: z - .string() - .optional() - .refine((val) => !val || /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/.test(val), { - message: "Invalid PAN number", - }), - permanentAddress: z - .string() - .min(1, { message: "Permanent Address is required" }) - .max(500, { message: "Address cannot exceed 500 characters" }), - phoneNumber: z - .string() - .min(1, { message: "Phone Number is required" }) - .regex(mobileNumberRegex, { message: "Invalid phone number " }), - jobRoleId: z.string().min(1, { message: "Role is required" }), - }); - useEffect(() => { - refetch() - }, []) + refetch(); + }, []); const { register, @@ -147,25 +60,8 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => { reset, getValues, } = useForm({ - resolver: zodResolver(userSchema), - defaultValues: { - id: currentEmployee?.id || null, - firstName: currentEmployee?.firstName || "", - middleName: currentEmployee?.middleName || "", - lastName: currentEmployee?.lastName || "", - email: currentEmployee?.email || "", - currentAddress: currentEmployee?.currentAddress || "", - birthDate: formatDate(currentEmployee?.birthDate) || "", - joiningDate: formatDate(currentEmployee?.joiningDate) || "", - emergencyPhoneNumber: currentEmployee?.emergencyPhoneNumber || "", - emergencyContactPerson: currentEmployee?.emergencyContactPerson || "", - aadharNumber: currentEmployee?.aadharNumber || "", - gender: currentEmployee?.gender || "", - panNumber: currentEmployee?.panNumber || "", - permanentAddress: currentEmployee?.permanentAddress || "", - phoneNumber: currentEmployee?.phoneNumber || "", - jobRoleId: currentEmployee?.jobRoleId.toString() || null, - }, + resolver: zodResolver(employeeSchema), + defaultValues: defatEmployeeObj, mode: "onChange", }); @@ -176,7 +72,13 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => { data.email = null; } - updateEmployee({ ...data, IsAllEmployee }, { + const payload = { ...data, IsAllEmployee }; + + if (employeeId) { + payload.id = employeeId; + } + + updateEmployee(payload, { onSuccess: () => { reset(); onClosed(); @@ -184,7 +86,6 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => { }); }; - useEffect(() => { if (!loading && !error && employee) { setCurrentEmployee(employee); @@ -195,37 +96,47 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => { reset( currentEmployee ? { - id: currentEmployee.id || null, - firstName: currentEmployee.firstName || "", - middleName: currentEmployee.middleName || "", - lastName: currentEmployee.lastName || "", - email: currentEmployee.email || "", - currentAddress: currentEmployee.currentAddress || "", - birthDate: formatDate(currentEmployee.birthDate) || "", - joiningDate: formatDate(currentEmployee.joiningDate) || "", - emergencyPhoneNumber: currentEmployee.emergencyPhoneNumber || "", - emergencyContactPerson: - currentEmployee.emergencyContactPerson || "", - aadharNumber: currentEmployee.aadharNumber || "", - gender: currentEmployee.gender || "", - panNumber: currentEmployee.panNumber || "", - permanentAddress: currentEmployee.permanentAddress || "", - phoneNumber: currentEmployee.phoneNumber || "", - jobRoleId: currentEmployee.jobRoleId?.toString() || "", - } + id: currentEmployee.id || null, + firstName: currentEmployee.firstName || "", + middleName: currentEmployee.middleName || "", + lastName: currentEmployee.lastName || "", + email: currentEmployee.email || "", + currentAddress: currentEmployee.currentAddress || "", + birthDate: formatDate(currentEmployee.birthDate) || "", + joiningDate: formatDate(currentEmployee.joiningDate) || "", + emergencyPhoneNumber: currentEmployee.emergencyPhoneNumber || "", + emergencyContactPerson: + currentEmployee.emergencyContactPerson || "", + aadharNumber: currentEmployee.aadharNumber || "", + gender: currentEmployee.gender || "", + panNumber: currentEmployee.panNumber || "", + permanentAddress: currentEmployee.permanentAddress || "", + phoneNumber: currentEmployee.phoneNumber || "", + jobRoleId: currentEmployee.jobRoleId?.toString() || "", + organizationId: currentEmployee.organizationId || "", + hasApplicationAccess: currentEmployee.hasApplicationAccess || false, + } : {} ); setCurrentAddressLength(currentEmployee?.currentAddress?.length || 0); setPermanentAddressLength(currentEmployee?.permanentAddress?.length || 0); }, [currentEmployee, reset]); + const hasAccessAplication = watch("hasApplicationAccess"); return ( <>
    -

    {employee ? "Update Employee" : "Create Employee"}

    +
    +

    + {" "} + {employee ? "Update Employee" : "Create Employee"} +

    {" "} +
    - + { }} /> {errors.firstName && ( -
    +
    {errors.firstName.message}
    )} @@ -267,14 +181,18 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => { }} /> {errors.middleName && ( -
    +
    {errors.middleName.message}
    )}
    -
    - + { }} /> {errors.lastName && ( -
    +
    {errors.lastName.message}
    )}
    -
    -
    Email
    + { )}
    - + {
    - +
    - - {" "} - {500 - currentAddressLength} characters left - + {500 - currentAddressLength} characters left
    {errors.currentAddress && (
    { }} >
    - - {500 - permanentAddressLength} characters left - + {500 - permanentAddressLength} characters left
    {errors.permanentAddress && (
    { )}
    + + {/* -------------- */} +
    +
    + +
    + +
    + {errors.organizationId && ( +
    + {errors.organizationId.message} +
    + )} +
    + +
    + +
    +
    + + {/* --------------- */}
    {" "}
    @@ -488,7 +469,9 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => {
    - +
    -
    -
    - )} -
    - -
    - ); }; diff --git a/src/components/Expenses/ExpenseFilterPanel.jsx b/src/components/Expenses/ExpenseFilterPanel.jsx index 5e415821..250f5de4 100644 --- a/src/components/Expenses/ExpenseFilterPanel.jsx +++ b/src/components/Expenses/ExpenseFilterPanel.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState,useMemo } from "react"; +import React, { useEffect, useState, useMemo } from "react"; import { FormProvider, useForm, Controller } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { defaultFilter, SearchSchema } from "./ExpenseSchema"; @@ -16,8 +16,11 @@ import { ExpenseFilterSkeleton } from "./ExpenseSkeleton"; import { useLocation } from "react-router-dom"; const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => { - const selectedProjectId = useSelector((store) => store.localVariables.projectId); - const { data, isLoading,isError,error,isFetching , isFetched} = useExpenseFilter(); + const selectedProjectId = useSelector( + (store) => store.localVariables.projectId + ); + const { data, isLoading, isError, error, isFetching, isFetched } = + useExpenseFilter(); const groupByList = useMemo(() => { return [ @@ -27,11 +30,10 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => { { id: "project", name: "Project" }, { id: "paymentMode", name: "Payment Mode" }, { id: "expensesType", name: "Expense Type" }, - { id: "createdAt", name: "Submitted Date" } + { id: "createdAt", name: "Submitted Date" }, ].sort((a, b) => a.name.localeCompare(b.name)); }, []); - const [selectedGroup, setSelectedGroup] = useState(groupByList[0]); const [resetKey, setResetKey] = useState(0); @@ -40,7 +42,7 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => { defaultValues: defaultFilter, }); - const { control, register, handleSubmit, reset, watch } = methods; + const { control, handleSubmit, reset, setValue, watch } = methods; const isTransactionDate = watch("isTransactionDate"); const closePanel = () => { @@ -78,28 +80,37 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => { }, [location]); if (isLoading || isFetching) return ; - if(isError && isFetched) return
    Something went wrong Here- {error.message}
    + if (isError && isFetched) + return
    Something went wrong Here- {error.message}
    ; return ( <> -
    - -
    - + +
    + +
    -
    - + {
    - + + +
    + ))} +
    + {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..122e707f --- /dev/null +++ b/src/components/Organization/ManagOrg.jsx @@ -0,0 +1,212 @@ +import React, { useEffect } from "react"; +import { FormProvider, useForm } from "react-hook-form"; +import { + useCreateOrganization, + useOrganization, + 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 { + data: organization, + isLoading: organizationLoading, + isError, + error, + } = useOrganization(orgData?.id); + + 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 (organization) { + reset({ + name: organization.name || "", + contactPerson: organization.contactPerson || "", + contactNumber: organization.contactNumber || "", + email: organization.email || "", + serviceIds: organization.services?.map((s) => s.id) || [], + address: organization.address || "", + }); + } + }, [organization, reset, service?.data]); + + const onSubmit = (formData) => { + let payload = { ...formData }; + if (organization?.id) { + updateOrganization({ + orgId: organization.id, + payload: { ...payload, id: organization.id }, + }); + } 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 ManageGroup; diff --git a/src/components/master/Services/ManageServices.jsx b/src/components/master/Services/ManageServices.jsx new file mode 100644 index 00000000..bdc379a2 --- /dev/null +++ b/src/components/master/Services/ManageServices.jsx @@ -0,0 +1,107 @@ +import React, { useEffect } from "react"; +import Label from "../../common/Label"; +import { useForm } from "react-hook-form"; +import { useCreateService, useUpdateService } from "../../../hooks/masterHook/useMaster"; +import { z } from "zod"; +import { zodResolver } from "@hookform/resolvers/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" }), +}); + +const ManageServices = ({ data , onClose }) => { + const { + register, + handleSubmit, + reset, + formState: { errors }, + } = useForm({ + resolver: zodResolver(schema), + defaultValues: { name: "", description: "" }, + }); + + const { mutate: CreateServices, isPending: Creating } = useCreateService(() => + onClose?.() + ); + const { mutate: UpdateServices, isPending: Updating } = useUpdateService(() => + onClose?.() + ); + + const onSubmit = (payload) => { + if (data && data.id) { + UpdateServices({ id: data.id, payload: { ...payload, id: data.id } }); + } else { + CreateServices(payload); + } + }; + + useEffect(() => { + if (data) { + reset({ + name: data.name ?? "", + description: data.description ?? "", + }); + } + }, [data, reset]); + + return ( +
    +
    + + + {errors.name &&

    {errors.name.message}

    } +
    + +
    + + + {errors.description && ( +

    {errors.description.message}

    + )} +
    + +
    + + +
    +
    + ); +}; + +export default ManageServices; diff --git a/src/components/master/Services/ServicesGroups.jsx b/src/components/master/Services/ServicesGroups.jsx new file mode 100644 index 00000000..b4262351 --- /dev/null +++ b/src/components/master/Services/ServicesGroups.jsx @@ -0,0 +1,263 @@ +import { useState } from "react"; +import { + useActivitiesByGroups, + useGroups, +} from "../../../hooks/masterHook/useMaster"; +import ManageGroup from "./ManageGroup"; +import ManageActivity from "./ManageActivity"; +import { useMasterContext } from "../../../pages/master/MasterPage"; +const ServiceGroups = ({ service }) => { + const [openService, setOpenService] = useState(true); + const [activeGroupId, setActiveGroupId] = useState(null); // track selected group + const {setDeleletingServiceItem} =useMasterContext() + const [isManageGroup, setManageGroup] = useState({ + isOpen: false, + group: null, + serviceId: null, + }); + const [isManageActivity, setManageActivity] = useState({ + isOpen: false, + activity: null, // activity is either a single activity for editing or null for creating new activity + groupId: null, // groupId for managing activities in specific groups + }); + + // Fetch groups and activities data + const { data: groups, isLoading } = useGroups(service?.id); // Fetch groups for the service + const { data: activities, isLoading: actLoading } = + useActivitiesByGroups(activeGroupId); // Fetch activities based on activeGroupId + + if (isLoading) return
    Loading groups...
    ; // Show loading state while groups are being fetched + + // Handle toggling of group to view activities + const toggleGroup = (groupId) => { + setActiveGroupId((prev) => (prev === groupId ? null : groupId)); // If the same group is clicked again, close it + }; + + return ( + +
    + +

    Manage Service

    +
    +
    + {/* Service Header */} +
    +

    {service.name}

    + +
    + + {/* Groups Section */} +
    + {/* Show ManageGroup for creating a new group */} + {isManageGroup.isOpen && isManageGroup.group === null ? ( + + setManageGroup({ + isOpen: false, + group: null, + serviceId: service.id, + }) + } + /> + ) : ( +
    +
    + + {groups?.data?.map((group) => { + const isOpen = activeGroupId === group.id; + + return ( +
    +
    + {/* Show group toggle button only if ManageGroup is not open */} +
    + {!isManageGroup.isOpen && ( + toggleGroup(group.id)} + className="text-end cursor-pointer" + data-bs-toggle="collapse" + data-bs-target={`#accordionGroup${group.id}`} + aria-expanded={isOpen} + aria-controls={`accordionGroup${group.id}`} + > + + + )} +

    {group.name}

    +
    +
    +
    + {/* Create New Activity */} + { + setManageActivity({ + isOpen: true, + activity: null, // Indicating new activity creation + groupId: group.id, // Set the groupId for the new activity + }); + }} + /> + {/* Edit Group */} + + setManageGroup({ + isOpen: true, + group: group, // Group selected for Editing + serviceId: service.id, + }) + } + /> + {/* Delete Group */} + setDeleletingServiceItem({isOpen:true,ItemId:group.id,whichItem:"group"})} /> +
    +
    +
    + + {/* Only show ManageGroup for the specific group if it's open */} + {isManageGroup.isOpen && + isManageGroup.group?.id === group.id ? ( + + setManageGroup({ + isOpen: false, + group: null, + serviceId: null, + }) + } + /> + ) : isManageActivity.isOpen && + isManageActivity.groupId === group.id ? ( + { + setManageActivity({ + isOpen: false, + activity: null, + groupId: null, + }); + }} + /> + ) : ( +
    +
    + {isOpen && actLoading && ( +

    + Loading activities... +

    + )} + + {isOpen && activities?.data?.length > 0 ? ( +
    + {/* Header Row */} +
    + + Activity Name + + + Unit of Measurement + + + Action + +
    + + {/* Map through activities */} + {activities.data.map((activity) => ( +
    + {/* Activity Name Column */} +
    + {activity.activityName} +
    + + {/* Unit of Measurement Column */} +
    + {activity.unitOfMeasurement} +
    + + {/* Action Column */} +
    + {/* Edit Activity */} + { + setManageActivity({ + isOpen: true, + activity: activity, // Pass the specific activity for editing + groupId: group.id, // Set groupId for the specific activity + }); + }} + /> + {/* Delete Activity */} + setDeleletingServiceItem({isOpen:true,ItemId:activity.id,whichItem:"activity"})} + /> +
    +
    + ))} +
    + ) : ( + isOpen && ( +

    + No activities found +

    + ) + )} +
    +
    + )} +
    + ); + })} +
    + )} +
    +
    +
    +
    + ); +}; + +export default ServiceGroups; diff --git a/src/components/master/Services/ServicesSchema.js b/src/components/master/Services/ServicesSchema.js new file mode 100644 index 00000000..72f28e50 --- /dev/null +++ b/src/components/master/Services/ServicesSchema.js @@ -0,0 +1,17 @@ +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" }), +}); + +export const ActivityGroupSchema = z.object({ + name: z.string().min(1, { message: "Group 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..5b31f79a 100644 --- a/src/hooks/masterHook/useMaster.js +++ b/src/hooks/masterHook/useMaster.js @@ -1,41 +1,77 @@ -import {useState,useEffect, useCallback} from "react" +import { useState, useEffect, useCallback } from "react"; import { MasterRespository } from "../../repositories/MastersRepository"; -import { cacheData,getCachedData } from "../../slices/apiDataManager"; +import { cacheData, getCachedData } from "../../slices/apiDataManager"; import { useSelector } from "react-redux"; -import {useMutation, useQueries, useQuery, useQueryClient} from "@tanstack/react-query"; +import { + useMutation, + useQueries, + useQuery, + useQueryClient, +} from "@tanstack/react-query"; import showToast from "../../services/toastService"; - - - - - - -export const useMasterMenu = ()=>{ +export const useServices = () => { return useQuery({ - queryKey:["MasterMenu"], - queryFn:async()=> { - const resp = await MasterRespository.getMasterMenus(); - return resp.data - } - }) -} + queryKey: ["services"], + queryFn: async () => await MasterRespository.getMasterServices(), + }); +}; -export const useActivitiesMaster = () => -{ - const { data:activities=[],isLoading:loading,error} = useQuery( { - queryKey: [ "ActivityMaster" ], - queryFn: async() => - { +export const useGroups = (serviceId) => { + return useQuery({ + queryKey: ["groups", serviceId], + queryFn: async () => await MasterRespository.getActivityGrops(serviceId), + enabled: !!serviceId, + }); +}; +export const useActivitiesByGroups = (groupId) => { + return useQuery({ + queryKey: ["activties", groupId], + queryFn: async () => await MasterRespository.getActivitesByGroup(groupId), + + enabled: !!groupId, + + }); +}; +export const useGlobalServices = () => { + return useQuery({ + queryKey: ["globalServices"], + queryFn: async () => await MasterRespository.getGlobalServices(), + }); +}; + +export const useMasterMenu = () => { + return useQuery({ + queryKey: ["MasterMenu"], + queryFn: async () => { + const resp = await MasterRespository.getMasterMenus(); + return resp.data; + }, + }); +}; + +export const useActivitiesMaster = () => { + const { + data: activities = [], + isLoading: loading, + error, + } = useQuery({ + queryKey: ["ActivityMaster"], + queryFn: async () => { const response = await MasterRespository.getActivites(); - return response.data + return response.data; }, - onError: (error) => { - showToast(error?.response?.data?.message || error.message || "Failed to fetch ActivityMasters", "error"); + onError: (error) => { + showToast( + error?.response?.data?.message || + error.message || + "Failed to fetch ActivityMasters", + "error" + ); }, - } ) - return {activities,loading,error} -} + }); + return { activities, loading, error }; +}; export const useWorkCategoriesMaster = () => { const { data: categories = [], @@ -60,22 +96,28 @@ export const useWorkCategoriesMaster = () => { return { categories, categoryLoading, categoryError }; }; - -export const useContactCategory = () => -{ - const {data:contactCategory=[],isLoading:loading,error:Error } = useQuery( { - queryKey: [ "Contact Category" ], - queryFn: async () => - { +export const useContactCategory = () => { + const { + data: contactCategory = [], + isLoading: loading, + error: Error, + } = useQuery({ + queryKey: ["Contact Category"], + queryFn: async () => { let resp = await MasterRespository.getContactCategory(); - return resp.data + return resp.data; }, - onError: (error) => { - showToast(error?.response?.data?.message || error.message || "Failed to fetch Contact categories", "error"); + onError: (error) => { + showToast( + error?.response?.data?.message || + error.message || + "Failed to fetch Contact categories", + "error" + ); }, - } ) - return { contactCategory,loading,Error} -} + }); + return { contactCategory, loading, Error }; +}; export const useContactTags = () => { const { @@ -101,16 +143,15 @@ export const useContactTags = () => { return { contactTags, loading, error }; }; - -export const useExpenseType =()=>{ -const { +export const useExpenseType = () => { + const { data: ExpenseTypes = [], isLoading: loading, error, } = useQuery({ queryKey: ["Expense Type"], queryFn: async () => { - const res = await MasterRespository.getExpenseType() + const res = await MasterRespository.getExpenseType(); return res.data; }, onError: (error) => { @@ -124,16 +165,16 @@ const { }); return { ExpenseTypes, loading, error }; -} -export const usePaymentMode =()=>{ -const { +}; +export const usePaymentMode = () => { + const { data: PaymentModes = [], isLoading: loading, error, } = useQuery({ queryKey: ["Payment Mode"], queryFn: async () => { - const res = await MasterRespository.getPaymentMode() + const res = await MasterRespository.getPaymentMode(); return res.data; }, onError: (error) => { @@ -147,16 +188,16 @@ const { }); return { PaymentModes, loading, error }; -} -export const useExpenseStatus =()=>{ -const { +}; +export const useExpenseStatus = () => { + const { data: ExpenseStatus = [], isLoading: loading, error, } = useQuery({ queryKey: ["Expense Status"], queryFn: async () => { - const res = await MasterRespository.getExpenseStatus() + const res = await MasterRespository.getExpenseStatus(); return res.data; }, onError: (error) => { @@ -170,20 +211,20 @@ const { }); return { ExpenseStatus, loading, error }; -} -export const useDocumentTypes =(category)=>{ -const { +}; +export const useDocumentTypes = (category) => { + const { data: DocumentTypes = [], error, isError, - isLoading + isLoading, } = useQuery({ - queryKey: ["Document Type",category], + queryKey: ["Document Type", category], queryFn: async () => { - const res = await MasterRespository.getDocumentTypes(category) + const res = await MasterRespository.getDocumentTypes(category); return res.data; }, - enabled:!!category, + enabled: !!category, onError: (error) => { showToast( error?.response?.data?.message || @@ -195,20 +236,20 @@ const { }); return { DocumentTypes, isError, isLoading, error }; -} -export const useDocumentCategories =(EntityType)=>{ -const { +}; +export const useDocumentCategories = (EntityType) => { + const { data: DocumentCategories = [], error, isError, - isLoading + isLoading, } = useQuery({ - queryKey: ["Document Category",EntityType], + queryKey: ["Document Category", EntityType], queryFn: async () => { - const res = await MasterRespository.getDocumentCategories(EntityType) + const res = await MasterRespository.getDocumentCategories(EntityType); return res.data; }, - enabled:!!EntityType, + enabled: !!EntityType, onError: (error) => { showToast( error?.response?.data?.message || @@ -220,7 +261,13 @@ 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 +278,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": @@ -274,8 +323,13 @@ const fetchMasterData = async (masterType) => { }; const useMaster = () => { - const selectedMaster = useSelector((store) => store.localVariables.selectedMaster); - const queryFn = useCallback(() => fetchMasterData(selectedMaster), [selectedMaster]); + const selectedMaster = useSelector( + (store) => store.localVariables.selectedMaster + ); + const queryFn = useCallback( + () => fetchMasterData(selectedMaster), + [selectedMaster] + ); const { data = [], isLoading, @@ -284,11 +338,16 @@ const useMaster = () => { queryKey: ["masterData", selectedMaster], queryFn, enabled: !!selectedMaster, - staleTime: 1000 * 60 * 10, + staleTime: 1000 * 60 * 10, refetchOnWindowFocus: false, onError: (error) => { - showToast(error?.response?.data?.message || error.message || `Failed to fetch ${selectedMaster} Maseter`, "error"); - }, + showToast( + error?.response?.data?.message || + error.message || + `Failed to fetch ${selectedMaster} Maseter`, + "error" + ); + }, }); return { data, loading: isLoading, error }; @@ -334,7 +393,7 @@ export const useCreateJobRole = (onSuccessCallback) => { onSuccess: (data) => { showToast("JobRole added successfully.", "success"); - queryClient.invalidateQueries({queryKey:["masterData", "Job Role"]}); + queryClient.invalidateQueries({ queryKey: ["masterData", "Job Role"] }); if (onSuccessCallback) onSuccessCallback(data); }, @@ -346,496 +405,581 @@ export const useCreateJobRole = (onSuccessCallback) => { // Application Role------------------------------------------- -export const useCreateApplicationRole = (onSuccessCallback) => -{ +export const useCreateApplicationRole = (onSuccessCallback) => { const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( payload ) => - { - const resp = await MasterRespository.createRole( payload ); + return useMutation({ + mutationFn: async (payload) => { + const resp = await MasterRespository.createRole(payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Application Role" ]} ) - showToast( "Application Role added successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Application Role"], + }); + showToast("Application Role added successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; -export const useUpdateApplicationRole = (onSuccessCallback) => -{ - const queryClient = useQueryClient(); +export const useUpdateApplicationRole = (onSuccessCallback) => { + const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ( {id, payload} ) => - { - const response = await MasterRespository.updateRoles(id,payload); + mutationFn: async ({ id, payload }) => { + const response = await MasterRespository.updateRoles(id, payload); return response.data; }, onSuccess: (data, variables) => { - queryClient.invalidateQueries({ queryKey: ["masterData", "Application Role"], }); showToast("Application Role updated successfully.", "success"); - + if (onSuccessCallback) onSuccessCallback(data); }, onError: (error) => { showToast(error.message || "Something went wrong", "error"); }, }); -} +}; -// Activity------------------------------ -export const useCreateActivity = (onSuccessCallback) => -{ - const queryClient = useQueryClient(); - - return useMutation( { - mutationFn: async ( payload ) => - { - const resp = await MasterRespository.createActivity(payload); - return resp.data; - }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Activity" ]} ) - showToast( "Activity added successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) - }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} - -export const useUpdateActivity = (onSuccessCallback) => -{ - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: async ( {id, payload} ) => - { - const response = await MasterRespository.updateActivity(id,payload); - return response.data; - }, - onSuccess: (data, variables) => { - - queryClient.invalidateQueries({ - queryKey: ["masterData", "Activity"], - }); - showToast("Activity updated successfully.", "success"); - - if (onSuccessCallback) onSuccessCallback(data); - }, - onError: (error) => { - showToast(error.message || "Something went wrong", "error"); - }, - }); -} //-----Create work Category------------------------------- -export const useCreateWorkCategory = (onSuccessCallback) => -{ +export const useCreateWorkCategory = (onSuccessCallback) => { const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( payload ) => - { + return useMutation({ + mutationFn: async (payload) => { const resp = await MasterRespository.createWorkCategory(payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries({queryKey: [ "masterData", "Work Category" ]} ) - showToast( "Work Category added successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Work Category"], + }); + showToast("Work Category added successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; -export const useUpdateWorkCategory = (onSuccessCallback) => -{ - const queryClient = useQueryClient(); +export const useUpdateWorkCategory = (onSuccessCallback) => { + const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ( {id, payload} ) => - { - const response = await MasterRespository.updateWorkCategory(id,payload); + mutationFn: async ({ id, payload }) => { + const response = await MasterRespository.updateWorkCategory(id, payload); return response.data; }, onSuccess: (data, variables) => { - queryClient.invalidateQueries({ queryKey: ["masterData", "Work Category"], }); showToast("Work Category updated successfully.", "success"); - + if (onSuccessCallback) onSuccessCallback(data); }, onError: (error) => { showToast(error.message || "Something went wrong", "error"); }, }); -} +}; //-- Contact Category--------------------------- - -export const useCreateContactCategory = (onSuccessCallback) => -{ +export const useCreateContactCategory = (onSuccessCallback) => { const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( payload ) => - { + return useMutation({ + mutationFn: async (payload) => { const resp = await MasterRespository.createContactCategory(payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Contact Category" ]} ) - showToast( "Contact Category added successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Contact Category"], + }); + showToast("Contact Category added successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; - -export const useUpdateContactCategory = (onSuccessCallback) => -{ - const queryClient = useQueryClient(); +export const useUpdateContactCategory = (onSuccessCallback) => { + const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ( {id, payload} ) => - { - const response = await MasterRespository.updateContactCategory(id,payload); + mutationFn: async ({ id, payload }) => { + const response = await MasterRespository.updateContactCategory( + id, + payload + ); return response.data; }, onSuccess: (data, variables) => { - queryClient.invalidateQueries({ queryKey: ["masterData", "Contact Category"], }); showToast("Contact Category updated successfully.", "success"); - + if (onSuccessCallback) onSuccessCallback(data); }, onError: (error) => { showToast(error.message || "Something went wrong", "error"); }, }); -} +}; // ---------Contact Tag------------------- -export const useCreateContactTag = (onSuccessCallback) => -{ +export const useCreateContactTag = (onSuccessCallback) => { const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( payload ) => - { + return useMutation({ + mutationFn: async (payload) => { const resp = await MasterRespository.createContactTag(payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Contact Tag" ]} ) - showToast( "Contact Tag added successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Contact Tag"], + }); + showToast("Contact Tag added successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; - -export const useUpdateContactTag = (onSuccessCallback) => -{ - const queryClient = useQueryClient(); +export const useUpdateContactTag = (onSuccessCallback) => { + const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ( {id, payload} ) => - { - debugger - const response = await MasterRespository.updateContactTag(id,payload); + mutationFn: async ({ id, payload }) => { + debugger; + const response = await MasterRespository.updateContactTag(id, payload); return response.data; }, onSuccess: (data, variables) => { - queryClient.invalidateQueries({ queryKey: ["masterData", "Contact Tag"], }); showToast("Contact Tag updated successfully.", "success"); - + if (onSuccessCallback) onSuccessCallback(data); }, onError: (error) => { showToast(error.message || "Something went wrong", "error"); }, }); -} +}; // ----------------------Expense Type------------------ -export const useCreateExpenseType = (onSuccessCallback)=>{ - const queryClient = useQueryClient(); +export const useCreateExpenseType = (onSuccessCallback) => { + const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( payload ) => - { + return useMutation({ + mutationFn: async (payload) => { const resp = await MasterRespository.createExpenseType(payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Expense Type" ]} ) - showToast( "Expense Type added successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Expense Type"], + }); + showToast("Expense Type added successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} -export const useUpdateExpenseType = (onSuccessCallback) => -{ - const queryClient = useQueryClient(); + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; +export const useUpdateExpenseType = (onSuccessCallback) => { + const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ( {id, payload} ) => - { - const response = await MasterRespository.updateExpenseType(id,payload); + mutationFn: async ({ id, payload }) => { + const response = await MasterRespository.updateExpenseType(id, payload); return response.data; }, onSuccess: (data, variables) => { - queryClient.invalidateQueries({ queryKey: ["masterData", "Expense Type"], }); showToast("Expense Type updated successfully.", "success"); - + if (onSuccessCallback) onSuccessCallback(data); }, onError: (error) => { showToast(error.message || "Something went wrong", "error"); }, }); -} +}; // -----------------Payment Mode ------------- -export const useCreatePaymentMode = (onSuccessCallback)=>{ - const queryClient = useQueryClient(); +export const useCreatePaymentMode = (onSuccessCallback) => { + const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( payload ) => - { + return useMutation({ + mutationFn: async (payload) => { const resp = await MasterRespository.createPaymentMode(payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Payment Mode" ]} ) - showToast( "Payment Mode added successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Payment Mode"], + }); + showToast("Payment Mode added successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} -export const useUpdatePaymentMode = (onSuccessCallback)=>{ - const queryClient = useQueryClient(); + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; - return useMutation( { - mutationFn: async ( {id,payload} ) => - { - const resp = await MasterRespository.updatePaymentMode(id,payload); +export const useUpdatePaymentMode = (onSuccessCallback) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async ({ id, payload }) => { + const resp = await MasterRespository.updatePaymentMode(id, payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Payment Mode" ]} ) - showToast( "Payment Mode Updated successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Payment Mode"], + }); + showToast("Payment Mode Updated successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; + +// 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?.response?.data?.message || 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?.response?.data?.message || error?.message || "Something went wrong", "error"); + }, + }); +}; + +export const useCreateActivityGroup = (onSuccessCallback) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (payload) => { + const response = await MasterRespository.createActivityGroup(payload); + return response; + }, + onSuccess: (data, variables) => { + queryClient.invalidateQueries({ + queryKey: ["groups"], + }); + debugger + showToast( data?.message || + data?.response?.data?.message || "Activity Group created successfully.", + "success" + ); + + if (onSuccessCallback) onSuccessCallback(data); + }, + onError: (error) => { + showToast(error?.response?.data?.message || error?.message || "Something went wrong", "error"); + }, + }); +}; +export const useUpdateActivityGroup = (onSuccessCallback) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async ({id,payload}) => { + const response = await MasterRespository.updateActivityGrop(id,payload); + return response; + }, + onSuccess: (data, variables) => { + queryClient.invalidateQueries({ + queryKey: ["groups"], + }); + + showToast( + data?.message || + data?.response?.data?.message|| "Activity Group Updated successfully.", + "success" + ); + + if (onSuccessCallback) onSuccessCallback(data); + }, + onError: (error) => { + showToast(error?.message || "Something went wrong", "error"); + }, + }); +}; +// Activity------------------------------ +export const useCreateActivity = (onSuccessCallback) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (payload) => { + const resp = await MasterRespository.createActivity(payload); + return resp.data; + }, + onSuccess: (data) => { + queryClient.invalidateQueries({ queryKey: ["activties"] }); + showToast("Activity added successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); + }, + onError: (error) => { + showToast(error?.response?.data?.message || error?.message || "Something went wrong", "error"); + }, + }); +}; + +export const useUpdateActivity = (onSuccessCallback) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async ({ id, payload }) => { + const response = await MasterRespository.updateActivity(id, payload); + return response.data; + }, + onSuccess: (data, variables) => { + queryClient.invalidateQueries({ + queryKey: ["activties"], + }); + showToast("Activity updated successfully.", "success"); + + if (onSuccessCallback) onSuccessCallback(data); + }, + onError: (error) => { + showToast(error?.response?.data?.message || error?.message || "Something went wrong", "error"); + }, + }); +}; // -------------------Expense Status---------------------------------- -export const useCreateExpenseStatus =(onSuccessCallback)=>{ - const queryClient = useQueryClient(); +export const useCreateExpenseStatus = (onSuccessCallback) => { + const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( payload ) => - { + return useMutation({ + mutationFn: async (payload) => { const resp = await MasterRespository.createExpenseStatus(payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Expense Status" ]} ) - showToast( "Expense Status added successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Expense Status"], + }); + showToast("Expense Status added successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} -export const useUpdateExpenseStatus = (onSuccessCallback)=>{ - const queryClient = useQueryClient(); + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; +export const useUpdateExpenseStatus = (onSuccessCallback) => { + const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( {id,payload} ) => - { - const resp = await MasterRespository.updateExepnseStatus(id,payload); + return useMutation({ + mutationFn: async ({ id, payload }) => { + const resp = await MasterRespository.updateExepnseStatus(id, payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Expense Status" ]} ) - showToast( "Expense Status Updated successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Expense Status"], + }); + showToast("Expense Status Updated successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} - - + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; // --------------------Document-Category-------------------------------- -export const useCreateDocumentCatgory =(onSuccessCallback)=>{ - const queryClient = useQueryClient(); +export const useCreateDocumentCatgory = (onSuccessCallback) => { + const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( payload ) => - { + return useMutation({ + mutationFn: async (payload) => { const resp = await MasterRespository.createDocumenyCategory(payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Document Category" ]} ) - queryClient.invalidateQueries( {queryKey:[ "Document Category" ]} ) - showToast( "Document Category added successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Document Category"], + }); + queryClient.invalidateQueries({ queryKey: ["Document Category"] }); + showToast("Document Category added successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; -export const useUpdateDocumentCategory =(onSuccessCallback)=>{ - const queryClient = useQueryClient(); +export const useUpdateDocumentCategory = (onSuccessCallback) => { + const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( {id,payload} ) => - { - const resp = await MasterRespository.updateDocumentCategory(id,payload); + return useMutation({ + mutationFn: async ({ id, payload }) => { + const resp = await MasterRespository.updateDocumentCategory(id, payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Document Category" ]} ) - queryClient.invalidateQueries( {queryKey:[ "Document Category" ]} ) - showToast( "Document Category Updated successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Document Category"], + }); + queryClient.invalidateQueries({ queryKey: ["Document Category"] }); + showToast("Document Category Updated successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; // ------------------------------Document-Type----------------------------------- -export const useCreateDocumentType =(onSuccessCallback)=>{ - const queryClient = useQueryClient(); +export const useCreateDocumentType = (onSuccessCallback) => { + const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( payload ) => - { + return useMutation({ + mutationFn: async (payload) => { const resp = await MasterRespository.createDocumentType(payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Document Type" ]} ) - showToast( "Document Type added successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Document Type"], + }); + showToast("Document Type added successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; -export const useUpdateDocumentType =(onSuccessCallback)=>{ - const queryClient = useQueryClient(); +export const useUpdateDocumentType = (onSuccessCallback) => { + const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( {id,payload} ) => - { - const resp = await MasterRespository.updateDocumentType(id,payload); + return useMutation({ + mutationFn: async ({ id, payload }) => { + const resp = await MasterRespository.updateDocumentType(id, payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Document Type" ]} ) - showToast( "Document Type Updated successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Document Type"], + }); + showToast("Document Type Updated successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; // -Delete Master -------- export const useDeleteMasterItem = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ( {masterType, item} ) => - { + mutationFn: async ({ masterType, item }) => { const deleteFn = MasterRespository[masterType]; if (!deleteFn) { - throw new Error(`No delete strategy defined for master type: ${masterType}`); + throw new Error( + `No delete strategy defined for master type: ${masterType}` + ); } await deleteFn(item.id); @@ -850,8 +994,55 @@ export const useDeleteMasterItem = () => { onError: (error) => { const message = - error?.response?.data?.message || error?.message || "Error occurred during deletion"; + error?.response?.data?.message || + error?.message || + "Error occurred during deletion"; showToast(message, "error"); }, }); -}; \ No newline at end of file +}; + + +export const useDeleteServiceGroup =()=>{ + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (id)=>await MasterRespository.deleteActivityGroup(id), + onSuccess: ({_,variable}) => { + + queryClient.invalidateQueries({ queryKey: ["groups"] }); + + showToast(`Group deleted successfully.`, "success"); + }, + + onError: (error) => { + const message = + error?.response?.data?.message || + error?.message || + "Error occurred during deletion"; + showToast(message, "error"); + }, + }); +} +export const useDeleteActivity =()=>{ + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (id)=>await MasterRespository.deleteActivity(id), + onSuccess: ({_,variable}) => { + + + queryClient.invalidateQueries({ queryKey: ["activties"] }); + + showToast(`Acivity deleted successfully.`, "success"); + }, + + onError: (error) => { + const message = + error?.response?.data?.message || + error?.message || + "Error occurred during deletion"; + showToast(message, "error"); + }, + }); +} 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..8e5b8de2 --- /dev/null +++ b/src/hooks/useAuth.jsx @@ -0,0 +1,87 @@ +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"; +import { removeSession } from "../utils/authUtils.js"; + +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) => { + if (localStorage.getItem("jwtToken")) { + localStorage.setItem("jwtToken", data.token); + localStorage.setItem("refreshToken", data.refreshToken); + } else { + sessionStorage.setItem("jwtToken", data.token); + sessionStorage.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") || sessionStorage.getItem("refreshToken") }; + return await AuthRepository.logout(payload); + }, + + onSuccess: (data) => { + removeSession() + + window.location.href = "/auth/login"; + if (onSuccessCallBack) onSuccessCallBack(); + }, + + onError: (error) => { + showToast(error.message || "Error while creating project", "error"); + removeSession() + }, + }); +}; 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/useEmployees.js b/src/hooks/useEmployees.js index 58a12e5a..477b18f7 100644 --- a/src/hooks/useEmployees.js +++ b/src/hooks/useEmployees.js @@ -221,7 +221,7 @@ export const useUpdateEmployee = () => { mutationFn: (employeeData) => EmployeeRepository.manageEmployee(employeeData), onSuccess: (_, variables) => { - const id = variables.id || variables.employeeId; + const id = variables?.id || variables?.employeeId; const isAllEmployee = variables.IsAllEmployee; // Cache invalidation diff --git a/src/hooks/useOrganization.js b/src/hooks/useOrganization.js new file mode 100644 index 00000000..e99d8ccb --- /dev/null +++ b/src/hooks/useOrganization.js @@ -0,0 +1,196 @@ +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()), + }; +}; + +// ================================Query============================================================= + +export const useOrganization=(id)=>{ +return useQuery({ + queryKey:["organization",id], + queryFn:async()=> { + const resp = await await OrganizationRepository.getOrganizaion(id); + return resp.data + }, + enabled:!!id +}) +} +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 useOrganizationEmployees = ( + projectId, + organizationId, + searchString +) => { + return useQuery({ + queryKey: ["OrgEmployees", projectId, organizationId, searchString], + queryFn: async () => + await OrganizationRepository.getOrganizationEmployees( + projectId, + organizationId, + searchString + ), + enabled: !!projectId , + }); +}; + +// =================================Mutation======================================================== + +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) => { + const { projectId } = variables; + useClient.invalidateQueries({ + queryKey: ["projectAssignedOrganiztions"], + }); + useClient.invalidateQueries({ + queryKey: ["projectAssignedOrganization", projectId], + }); + 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 = (onSuccessCallback) => { + const useClient = useQueryClient(); + return useMutation({ + mutationFn: async ({orgId,payload}) => + await OrganizationRepository.updateOrganizaion(orgId,payload), + onSuccess: (_, variables) => { + useClient.invalidateQueries({ queryKey: ["organizationList"] }); + showToast("Organization Updated 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/useProjectAccess.js b/src/hooks/useProjectAccess.js index a3ff27ac..d932b157 100644 --- a/src/hooks/useProjectAccess.js +++ b/src/hooks/useProjectAccess.js @@ -6,21 +6,24 @@ import { VIEW_PROJECTS } from "../utils/constants"; import showToast from "../services/toastService"; export const useProjectAccess = (projectId) => { + const navigate = useNavigate(); + const { data: projectPermissions, isLoading, isFetched } = useAllProjectLevelPermissions(projectId); - const canView = useHasUserPermission(VIEW_PROJECTS); - const navigate = useNavigate(); + const canView = useHasUserPermission(VIEW_PROJECTS); + + const loading = isLoading || !isFetched; useEffect(() => { - if (projectId && isFetched && !isLoading && !canView) { + if (projectId && !loading && !canView) { showToast("You don't have permission to view project details", "warning"); navigate("/projects"); } - }, [projectId, isFetched, isLoading, canView, navigate]); + }, [projectId, loading, canView, navigate]); return { canView, - loading: isLoading || !isFetched, + loading, }; -}; +}; \ No newline at end of file diff --git a/src/hooks/useProjects.js b/src/hooks/useProjects.js index 5650f0fa..28c65a0b 100644 --- a/src/hooks/useProjects.js +++ b/src/hooks/useProjects.js @@ -14,17 +14,15 @@ import { } from "@tanstack/react-query"; import showToast from "../services/toastService"; +export const useCurrentService = () => { + return useSelector((store) => store.globalVariables.selectedServiceId); +}; + // ------------------------------Query------------------- export const useProjects = () => { const loggedUser = useSelector((store) => store.globalVariables.loginUser); - - const { - data: projects = [], - isLoading: loading, - error, - refetch, - } = useQuery({ + return useQuery({ queryKey: ["ProjectsList"], queryFn: async () => { const response = await ProjectRepository.getProjectList(); @@ -32,31 +30,40 @@ export const useProjects = () => { }, enabled: !!loggedUser, }); - - return { - projects, - loading, - error, - refetch, - }; }; -export const useEmployeesByProjectAllocated = (selectedProject) => { +export const useEmployeesByProjectAllocated = ( + projectId, + serviceId, + organizationId, + emloyeeeStatus +) => { const { data = [], isLoading, refetch, error, } = useQuery({ - queryKey: ["empListByProjectAllocated", selectedProject], + queryKey: [ + "empListByProjectAllocated", + projectId, + serviceId, + organizationId, + emloyeeeStatus, + ], queryFn: async () => { - const res = await ProjectRepository.getProjectAllocation(selectedProject); - return res.data || res; + const res = await ProjectRepository.getProjectAllocation( + projectId, + serviceId, + organizationId, + emloyeeeStatus + ); + 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" ); }, @@ -172,17 +179,20 @@ export const useProjectName = () => { }; }; -export const useProjectInfra = (projectId) => { +export const useProjectInfra = (projectId, serviceId) => { const { data: projectInfra, isLoading, error, - isFetched + isFetched, } = useQuery({ - queryKey: ["ProjectInfra", projectId], + queryKey: ["ProjectInfra", projectId, serviceId], queryFn: async () => { if (!projectId) return null; - const res = await ProjectRepository.getProjectInfraByproject(projectId); + const res = await ProjectRepository.getProjectInfraByproject( + projectId, + serviceId + ); return res.data; }, enabled: !!projectId, @@ -191,26 +201,30 @@ 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,14 +282,55 @@ 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, + }); +}; + +export const useEmployeeForTaskAssign = ( + projectId, + serviceId, + organizationId +) => { + return useQuery({ + queryKey: ["EmployeeForTaskAssign", projectId, serviceId, organizationId], + queryFn: async () => + await ProjectRepository.getEmployeeForTaskAssign( + projectId, + serviceId, + organizationId + ), + }); +}; + // -- -------------Mutation------------------------------- export const useCreateProject = ({ onSuccessCallback }) => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: async (newProject) => { - const res = await ProjectRepository.manageProject(newProject); + mutationFn: async (payload) => { + const res = await ProjectRepository.manageProject(payload); return res.data; }, onSuccess: (data) => { @@ -301,11 +356,11 @@ export const useCreateProject = ({ onSuccessCallback }) => { }); }; -export const useUpdateProject = ({ onSuccessCallback }) => { +export const useUpdateProject = ( onSuccessCallback ) => { const queryClient = useQueryClient(); const { mutate, isPending, isSuccess, isError } = useMutation({ - mutationFn: async ({ projectId, updatedData }) => { - return await ProjectRepository.updateProject(projectId, updatedData); + mutationFn: async ({ projectId, payload }) => { + return await ProjectRepository.updateProject(projectId, payload); }, onSuccess: (data, variables) => { @@ -365,8 +420,8 @@ export const useManageProjectAllocation = ({ const queryClient = useQueryClient(); const { mutate, isPending, isSuccess, isError } = useMutation({ - mutationFn: async ({ items }) => { - const response = await ProjectRepository.manageProjectAllocation(items); + mutationFn: async ({ payload }) => { + const response = await ProjectRepository.manageProjectAllocation(payload); return response.data; }, onSuccess: (data, variables, context) => { @@ -374,7 +429,7 @@ export const useManageProjectAllocation = ({ queryKey: ["empListByProjectAllocated"], }); queryClient.removeQueries({ queryKey: ["projectEmployees"] }); - if (variables?.added) { + if (variables.actionType === "assign") { showToast("Employee Assigned Successfully", "success"); } else { showToast("Removed Employee Successfully", "success"); diff --git a/src/hooks/useTasks.js b/src/hooks/useTasks.js index b793ced4..5073c115 100644 --- a/src/hooks/useTasks.js +++ b/src/hooks/useTasks.js @@ -8,30 +8,25 @@ import { useSelector } from "react-redux"; // ---------Query--------------------------------- -export const useTaskList = (projectId, dateFrom, toDate) => { - const enabled = !!projectId && !!dateFrom && !!toDate; +export const useTaskList = (projectId, pageSize, pageNumber, serviceId, filter) => { - const { - data: TaskList = [], - isLoading: loading, - error, - refetch, - } = useQuery({ - queryKey: ["taskList", projectId, dateFrom, toDate], + return useQuery({ + queryKey: ["taskList", projectId, pageSize, pageNumber, serviceId, filter], queryFn: async () => { const response = await TasksRepository.getTaskList( projectId, - dateFrom, - toDate + pageSize, + pageNumber, + serviceId, + filter ); return response.data; }, - enabled, + enabled:!!projectId }); - - return { TaskList, loading, error, refetch }; }; + export const useTaskById = (TaskId) => { const { data: Task = null, diff --git a/src/main.tsx b/src/main.tsx index ac7175fe..90385d5b 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 './pages/Gallary/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 = () => {
  • - {/* Single search input that moves */} -
    + {/* Search + Organization filter */} +
    + {/* Organization Dropdown */} + + + {/* Search Input */} { style={{ minWidth: "200px" }} />
    + +
    @@ -191,6 +218,7 @@ const AttendancePage = () => { handleModalData={handleModalData} getRole={getRole} searchTerm={searchTerm} + organizationId={appliedFilters.selectedOrganization} />
    )} @@ -199,12 +227,16 @@ const AttendancePage = () => {
    )} {activeTab === "regularization" && DoRegularized && (
    - +
    )} diff --git a/src/pages/Activities/DailyTask.jsx b/src/pages/Activities/DailyTask.jsx deleted file mode 100644 index fc51f756..00000000 --- a/src/pages/Activities/DailyTask.jsx +++ /dev/null @@ -1,251 +0,0 @@ -import React, { useEffect, useMemo, useState } from "react"; -import { useDispatch } from "react-redux"; -import { useTaskList } from "../../hooks/useTasks"; -import { useProjectName } from "../../hooks/useProjects"; -import { setProjectId } from "../../slices/localVariablesSlice"; -import Breadcrumb from "../../components/common/Breadcrumb"; -import DateRangePicker from "../../components/common/DateRangePicker"; -import FilterIcon from "../../components/common/FilterIcon"; -import GlobalModel from "../../components/common/GlobalModel"; -import ReportTask from "../../components/Activities/ReportTask"; -import ReportTaskComments from "../../components/Activities/ReportTaskComments"; -import SubTask from "../../components/Activities/SubTask"; -import { formatNumber, formatUTCToLocalTime } from "../../utils/dateUtils"; -import { useHasUserPermission } from "../../hooks/useHasUserPermission"; -import { APPROVE_TASK, ASSIGN_REPORT_TASK } from "../../utils/constants"; -import { useSelectedProject } from "../../slices/apiDataManager"; -import moment from "moment"; -import Loader from "../../components/common/Loader"; - -const DailyTask = () => { - const dispatch = useDispatch(); - const selectedProject = useSelectedProject(); - const { projectNames } = useProjectName(); - const ApprovedTaskRights = useHasUserPermission(APPROVE_TASK); - const ReportTaskRights = useHasUserPermission(ASSIGN_REPORT_TASK); - - const [filters, setFilters] = useState({ - selectedBuilding: "", - selectedFloors: [], - selectedActivities: [], - }); - - const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" }); - - const { TaskList, loading: taskLoading } = useTaskList( - selectedProject || null, - dateRange?.startDate || null, - dateRange?.endDate || null - ); - - // Ensure project is set - useEffect(() => { - if (!selectedProject && projectNames.length > 0) { - debugger - dispatch(setProjectId(projectNames[0].id)); - } - }, [selectedProject, projectNames, dispatch]); - - // 🔹 Reset filters when project changes - useEffect(() => { - setFilters({ - selectedBuilding: "", - selectedFloors: [], - selectedActivities: [], - }); - }, [selectedProject]); - - // Memoized filtering - const filteredTasks = useMemo(() => { - if (!TaskList) return []; - return TaskList.filter((task) => { - const { selectedBuilding, selectedFloors, selectedActivities } = filters; - - if (selectedBuilding && task?.workItem?.workArea?.floor?.building?.name !== selectedBuilding) return false; - if (selectedFloors.length > 0 && !selectedFloors.includes(task?.workItem?.workArea?.floor?.floorName)) return false; - if (selectedActivities.length > 0 && !selectedActivities.includes(task?.workItem?.activityMaster?.activityName)) return false; - - return true; - }); - }, [TaskList, filters]); - - // Memoized dates - const groupedTasks = useMemo(() => { - const groups = {}; - filteredTasks.forEach((task) => { - const date = task.assignmentDate.split("T")[0]; - if (!groups[date]) groups[date] = []; - groups[date].push(task); - }); - return Object.keys(groups) - .sort((a, b) => new Date(b) - new Date(a)) - .map((date) => ({ date, tasks: groups[date] })); - }, [filteredTasks]); - - // --- Modal State - const [modal, setModal] = useState({ type: null, data: null }); - - const openModal = (type, data = null) => setModal({ type, data }); - const closeModal = () => setModal({ type: null, data: null }); - - // --- Render helpers - const renderTeamMembers = (task, refIndex) => ( -
    - ${task.teamMembers - .map( - (m) => ` -
    -
    - - ${m?.firstName?.charAt(0) || ""}${m?.lastName?.charAt(0) || ""} - -
    - ${m.firstName} ${m.lastName} -
    ` - ) - .join("")} -
    - `} - > - {task.teamMembers.slice(0, 3).map((m) => ( -
    - - {m?.firstName.slice(0, 1)} - -
    - ))} - {task.teamMembers.length > 3 && ( -
    - +{task.teamMembers.length - 3} -
    - )} -
    - ); - - return ( - <> - {/* --- Modals --- */} - {modal.type === "report" && ( - - - - )} - {modal.type === "comments" && ( - - { - if (isSubTask) openModal("subtask", modal.data.task); - else closeModal(); - }} - closeModal={closeModal} - /> - - )} - {modal.type === "subtask" && ( - - - - )} - -
    - - -
    -
    - {!selectedProject && (
    Please Select Project
    )} - {/* --- Filters --- */} -
    - - -
    - - {/* --- Table --- */} -
    - - - - - - - - - - - - - {taskLoading && ( - - - - )} - {!taskLoading && groupedTasks.length === 0 && ( - - - - )} - {!taskLoading && - groupedTasks.map(({ date, tasks }) => ( - - - - - {tasks.map((task, idx) => ( - - - - - - - - - ))} - - ))} - -
    ActivityAssignedCompletedAssign OnTeamActions
    - -
    - No reports available for the selected date range. -
    {formatUTCToLocalTime(date)}
    -
    {task.workItem.activityMaster?.activityName || "No Activity Name"}
    -
    - {task.workItem.workArea?.floor?.building?.name} › {task.workItem.workArea?.floor?.floorName} › {task.workItem.workArea?.areaName} -
    -
    {formatNumber(task.plannedTask)} / {formatNumber(task.workItem.plannedWork - task.workItem.completedWork)}{task.completedTask}{formatUTCToLocalTime(task.assignmentDate)}{renderTeamMembers(task, idx)} -
    - {ReportTaskRights && !task.reportedDate && ( - - )} - {ApprovedTaskRights && task.reportedDate && !task.approvedBy && ( - - )} - -
    -
    -
    - -
    -
    -
    - - ); -}; -export default DailyTask; diff --git a/src/pages/Activities/TaskPlannng.jsx b/src/pages/Activities/TaskPlannng.jsx index 25cf63f2..8728240f 100644 --- a/src/pages/Activities/TaskPlannng.jsx +++ b/src/pages/Activities/TaskPlannng.jsx @@ -1,23 +1,34 @@ -import React,{useEffect,useRef} from "react"; +import React, { useEffect, useState } from "react"; import Breadcrumb from "../../components/common/Breadcrumb"; import InfraPlanning from "../../components/Activities/InfraPlanning"; -import { useProjectName } from "../../hooks/useProjects"; -import { useDispatch, useSelector } from "react-redux"; +import { useCurrentService, useProjectName } from "../../hooks/useProjects"; +import { useDispatch } from "react-redux"; import { setProjectId } from "../../slices/localVariablesSlice"; import { useSelectedProject } from "../../slices/apiDataManager"; +import { useProjectAssignedServices } from "../../hooks/useProjects"; +import { setService } from "../../slices/globalVariablesSlice"; -const TaskPlannng = () => { -const selectedProject = useSelectedProject(); -const dispatch = useDispatch(); -const { projectNames = [], loading: projectLoading } = useProjectName(); +const TaskPlanning = () => { + const selectedProject = useSelectedProject(); + const selectedService = useCurrentService(); + const dispatch = useDispatch(); -useEffect(() => { - if (!selectedProject) { - dispatch(setProjectId(projectNames[0]?.id)); + const { projectNames = [], loading: projectLoading } = useProjectName(); + + const { data, isLoading: servicesLoading } = + useProjectAssignedServices(selectedProject); + + // Set default project if none selected + useEffect(() => { + if (!selectedProject && projectNames.length > 0) { + dispatch(setProjectId(projectNames[0]?.id)); + } + }, [projectNames, selectedProject, dispatch]); + + // Loading state + if (projectLoading) { + return
    Loading...
    ; } -}, [projectNames, selectedProject?.id, dispatch]); - - return (
    { { label: "Daily Task Planning" }, ]} /> - {selectedProject ? ( - - ) : ( -
    Please Select Project
    - )} + +
    +
    + {data?.length === 0 ? ( +

    Service not assigned

    + ) : ( + + )} +
    + + {/* Planning Component */} + {selectedProject ? ( + + ) : ( +
    Please select a project
    + )} +
    ); }; -export default TaskPlannng; +export default TaskPlanning; diff --git a/src/pages/DailyProgressReport/DailyProgrssReport.jsx b/src/pages/DailyProgressReport/DailyProgrssReport.jsx new file mode 100644 index 00000000..96d9de6c --- /dev/null +++ b/src/pages/DailyProgressReport/DailyProgrssReport.jsx @@ -0,0 +1,119 @@ +import React, { createContext, useContext, useEffect, useState } from "react"; +import Breadcrumb from "../../components/common/Breadcrumb"; +import { useServices } from "../../hooks/masterHook/useMaster"; +import TaskReportList from "../../components/DailyProgressRport/TaskReportList"; +import GlobalModel from "../../components/common/GlobalModel"; +import ReportTaskComments from "../../components/Activities/ReportTaskComments"; +import ReportTask from "../../components/Activities/ReportTask"; +import TaskReportFilterPanel from "../../components/DailyProgressRport/TaskReportFilterPanel"; +import { useFab } from "../../Context/FabContext"; + +const DailyProgrssContext = createContext(); +export const useDailyProgrssContext = () => { + const context = useContext(DailyProgrssContext); + if (!context) { + throw new Error( + "useDailyTaskContext must be used within a DailyTaskProvider" + ); + } + return context; +}; + +const DailyProgrssReport = () => { + const [service, setService] = useState(""); + const [filter,setFilter] = useState('') + const { setOffcanvasContent, setShowTrigger } = useFab(); + const { data, isLoading, isError, error } = useServices(); + + const [modal, setModal] = useState({ type: null, data: null }); + + const openModal = (type, data = null) => setModal({ type, data }); + const closeModal = () => setModal({ type: null, data: null }); + + const contextDispatcher = { + service, + openModal, + closeModal, + filter, + }; + + const handleFilter = (filterObj)=>{ + setFilter(filterObj) + } + + useEffect(() => { + setShowTrigger(true); + setOffcanvasContent("Report Filter", ); + + return () => { + setShowTrigger(false); + setOffcanvasContent("", null); + }; + }, []); + return ( +
    + + {modal.type === "report" && ( + + + + )} + {modal.type === "comments" && ( + + { + if (isSubTask) openModal("subtask", modal.data.task); + else closeModal(); + }} + closeModal={closeModal} + /> + + )} + {modal.type === "subtask" && ( + + + + )} + + + +
    +
    + +
    +
    + +
    +
    +
    +
    + ); +}; + +export default DailyProgrssReport; diff --git a/src/pages/Directory/ContactsPage.jsx b/src/pages/Directory/ContactsPage.jsx index d3e84e6e..670b3ef9 100644 --- a/src/pages/Directory/ContactsPage.jsx +++ b/src/pages/Directory/ContactsPage.jsx @@ -69,7 +69,7 @@ const ContactsPage = ({ projectId, searchText, onExport }) => { }; if (isError) return
    {error.message}
    ; - if (isLoading) return gridView ? : ; + // if (isLoading) return gridView ? : ; return (
    @@ -94,11 +94,11 @@ const ContactsPage = ({ projectId, searchText, onExport }) => { ) : (
    } diff --git a/src/pages/Expense/ExpensePage.jsx b/src/pages/Expense/ExpensePage.jsx index 02af6286..5d2ef806 100644 --- a/src/pages/Expense/ExpensePage.jsx +++ b/src/pages/Expense/ExpensePage.jsx @@ -131,9 +131,8 @@ const ExpensePage = () => {
    {IsCreatedAble && ( )}
    diff --git a/src/pages/Organization/OrganizationPage.jsx b/src/pages/Organization/OrganizationPage.jsx new file mode 100644 index 00000000..21a28263 --- /dev/null +++ b/src/pages/Organization/OrganizationPage.jsx @@ -0,0 +1,53 @@ +import React, { useState } from "react"; +import Breadcrumb from "../../components/common/Breadcrumb"; +import { useOrganizationModal } from "../../hooks/useOrganization"; +import OrganizationsList from "../../components/Organization/OrganizationsList"; + +const OrganizationPage = () => { + const { isOpen, orgData, startStep, onOpen, flowType } = + useOrganizationModal(); + const [searchText, setSearchText] = useState("") + + return ( +
    + +
    +
    +
    +
    +
    + setSearchText(e.target.value)} + className="form-control form-control-sm w-auto" + placeholder="Search Organization" + aria-describedby="search-label" + /> +
    +
    + +
    + +
    +
    +
    +
    + + +
    + ); +}; + +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..694ff557 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), @@ -127,38 +127,27 @@ const TenantPage = () => {
    {/* Search */} -
    +
    setSearchText(e.target.value)} - className="form-control form-control" placeholder="Search Tenant" />
    {/* Actions */} -
    - refetchFn && refetchFn()} - > - Refresh{" "} - - - +
    diff --git a/src/pages/authentication/LoginPage.jsx b/src/pages/authentication/LoginPage.jsx index 3e331376..6be7cb2b 100644 --- a/src/pages/authentication/LoginPage.jsx +++ b/src/pages/authentication/LoginPage.jsx @@ -16,13 +16,13 @@ const LoginPage = () => { const loginSchema = IsLoginWithOTP ? z.object({ - username: z.string().trim().email({ message: "Valid email required" }), - }) + username: z.string().trim().email({ message: "Valid email required" }), + }) : z.object({ - username: z.string().trim().email({ message: "Valid email required" }), - password: z.string().trim().min(1, { message: "Password required" }), - rememberMe: z.boolean(), - }); + username: z.string().trim().email({ message: "Valid email required" }), + password: z.string().trim().min(1, { message: "Password required" }), + rememberMe: z.boolean(), + }); const { register, @@ -41,10 +41,15 @@ const LoginPage = () => { password: data.password, }; const response = await AuthRepository.login(userCredential); - localStorage.setItem("jwtToken", response.data.token); - localStorage.setItem("refreshToken", response.data.refreshToken); + if (data.rememberMe) { + localStorage.setItem("jwtToken", response.data.token); + localStorage.setItem("refreshToken", response.data.refreshToken); + } else { + sessionStorage.setItem("jwtToken", response.data.token); + sessionStorage.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"); @@ -69,6 +74,16 @@ const LoginPage = () => { } }, [IsLoginWithOTP]); + useEffect(() => { + const token = + localStorage.getItem("jwtToken") || + sessionStorage.getItem("jwtToken"); + + if (token) { + navigate("/dashboard", { replace: true }); + } +}, []); + return (
    @@ -106,36 +121,6 @@ const LoginPage = () => { {/* Password */} {!IsLoginWithOTP && ( <> - {/*
    - -
    - - setHidepass(!hidepass)} - > - - -
    - {errors.password && ( -
    - {errors.password.message} -
    - )} -
    */} -
    - {/* ✅ Error message */} {errors.password && ( -
    +
    {errors.password.message}
    )}
    - {/* Remember Me + Forgot Password */}
    @@ -209,8 +196,8 @@ const LoginPage = () => { {loading ? "Please Wait..." : IsLoginWithOTP - ? "Send OTP" - : "Sign In"} + ? "Send OTP" + : "Sign In"} {/* Login With OTP Button */} @@ -254,4 +241,4 @@ const LoginPage = () => { ); }; -export default LoginPage; \ No newline at end of file +export default LoginPage; diff --git a/src/pages/authentication/LoginWithOtp.jsx b/src/pages/authentication/LoginWithOtp.jsx index 8b07f8fb..a1af0e40 100644 --- a/src/pages/authentication/LoginWithOtp.jsx +++ b/src/pages/authentication/LoginWithOtp.jsx @@ -52,7 +52,7 @@ const LoginWithOtp = () => { setLoading(false); localStorage.removeItem("otpUsername"); localStorage.removeItem("otpSentTime"); - navigate("/dashboard"); + navigate("/auth/switch/org"); } catch (err) { showToast("Invalid or expired OTP.", "error"); diff --git a/src/pages/authentication/MainForgetPage.jsx b/src/pages/authentication/MainForgetPage.jsx index caae1016..ea6bdd4c 100644 --- a/src/pages/authentication/MainForgetPage.jsx +++ b/src/pages/authentication/MainForgetPage.jsx @@ -7,7 +7,7 @@ const MainForgetPage = () => { <>
    -
    +
    { <>
    -
    +
    { + 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..18028902 --- /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")) { + chooseTenant(localStorage.getItem("ctnt")) + } + }, [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..fc77ba1c 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( @@ -751,7 +751,6 @@ const EmployeeList = () => {
    ) : ( - //
    diff --git a/src/pages/master/MasterPage.jsx b/src/pages/master/MasterPage.jsx index 1640f2ba..a206b913 100644 --- a/src/pages/master/MasterPage.jsx +++ b/src/pages/master/MasterPage.jsx @@ -1,4 +1,4 @@ -import React, { useState, useMemo, useEffect } from "react"; +import React, { useState, useMemo, useEffect, createContext, useContext } from "react"; import { useDispatch, useSelector } from "react-redux"; import { useQueryClient } from "@tanstack/react-query"; import Breadcrumb from "../../components/common/Breadcrumb"; @@ -6,7 +6,9 @@ import MasterModal from "../../components/master/MasterModal"; import ConfirmModal from "../../components/common/ConfirmModal"; import MasterTable from "./MasterTable"; import useMaster, { + useDeleteActivity, useDeleteMasterItem, + useDeleteServiceGroup, useMasterMenu, } from "../../hooks/masterHook/useMaster"; import { changeMaster } from "../../slices/localVariablesSlice"; @@ -14,6 +16,16 @@ import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { MANAGE_MASTER } from "../../utils/constants"; import GlobalModel from "../../components/common/GlobalModel"; + +export const MasterContext = createContext(); +export const useMasterContext = () => { + const context = useContext(MasterContext); + if (!context) { + throw new Error("useMasterContext must be used within an MasterProvider"); + } + return context; +}; + const MasterPage = () => { const dispatch = useDispatch(); const queryClient = useQueryClient(); @@ -34,6 +46,9 @@ const MasterPage = () => { isError: isMasterError, } = useMaster(); const { mutate: DeleteMaster, isPending: isDeleting } = useDeleteMasterItem(); + const [isDeleletingServiceItem,setDeleletingServiceItem] = useState({isOpen:false,ItemId:null,whichItem:null}) + const {mutate:DeleteSericeGroup,isPending:deletingGroup} =useDeleteServiceGroup() + const {mutate:DeleteAcivity,isPending:deletingActivity} = useDeleteActivity() const [modalConfig, setModalConfig] = useState(null); const [deleteData, setDeleteData] = useState(null); @@ -73,6 +88,19 @@ const MasterPage = () => { ); }; + + const handleDeleteServiceItem =()=>{ + if(!isDeleletingServiceItem.ItemId) return + debugger + if(isDeleletingServiceItem.whichItem === "activity"){ + DeleteAcivity(isDeleletingServiceItem.ItemId,{onSuccess:()=>setDeleletingServiceItem({isOpen:false,ItemId:null,whichItem:null})}) + }else{ + DeleteSericeGroup(isDeleletingServiceItem.ItemId,{onSuccess:()=>setDeleletingServiceItem({isOpen:false,ItemId:null,whichItem:null})}) + } + + + } + if (menuErrorFlag || isMasterError) return (
    @@ -87,11 +115,11 @@ const MasterPage = () => { ); return ( - <> + {modalConfig && ( { /> )} + { onSubmit={handleDeleteSubmit} onClose={() => setDeleteData(null)} /> + + setDeleletingServiceItem({isOpen:false,ItemId:null,whichItem:null})} + />
    {
    - + ); }; diff --git a/src/pages/master/MasterTable.jsx b/src/pages/master/MasterTable.jsx index b2285842..8facfdb7 100644 --- a/src/pages/master/MasterTable.jsx +++ b/src/pages/master/MasterTable.jsx @@ -158,6 +158,21 @@ const MasterTable = ({ data, columns, loading, handleModalData }) => { ) : ( <> + {selectedMaster === "Services" && ( + + )} + - -
    - -
    - -
      - {[ - { - id: "b74da4c2-d07e-46f2-9919-e75e49b12731", - label: "Active", - }, - { - id: "cdad86aa-8a56-4ff4-b633-9c629057dfef", - label: "In Progress", - }, - { - id: "603e994b-a27f-4e5d-a251-f3d69b0498ba", - label: "On Hold", - }, - { - id: "ef1c356e-0fe0-42df-a5d3-8daee355492d", - label: "Inactive", - }, - { - id: "33deaef9-9af1-4f2a-b443-681ea0d04f81", - label: "Completed", - }, - ].map(({ id, label }) => ( -
    • -
      - handleStatusChange(id)} - /> - -
      -
    • - ))} -
    -
    -
    - -
    - -
    -
    -
    -
    - {loading &&

    Loading...

    } - {!loading && filteredProjects.length === 0 && !listView && ( -

    No projects found.

    - )} - - {listView ? ( -
    -
    -
    - - - - - - - - - - - - - - - {currentItems.length === 0 ? ( - - - - ) : ( - currentItems.map((project) => ( - - )) - )} - -
    - Project Name - Contact PersonSTART DATEDEADLINETaskProgress -
    - -
      - {[ - { - id: "b74da4c2-d07e-46f2-9919-e75e49b12731", - label: "Active", - }, - { - id: "cdad86aa-8a56-4ff4-b633-9c629057dfef", - label: "In Progress", - }, - { - id: "603e994b-a27f-4e5d-a251-f3d69b0498ba", - label: "On Hold", - }, - { - id: "ef1c356e-0fe0-42df-a5d3-8daee355492d", - label: "Inactive", - }, - { - id: "33deaef9-9af1-4f2a-b443-681ea0d04f81", - label: "Completed", - }, - ].map(({ id, label }) => ( -
    • -
      - handleStatusChange(id)} - /> - -
      -
    • - ))} -
    -
    -
    - Action -
    - No projects found -
    -
    {" "} -
    {" "} -
    - ) : ( -
    - {currentItems.map((project) => ( - - ))} -
    - )} - - {!loading && totalPages > 1 && ( - - )} -
    - - ); -}; - -export default ProjectList; diff --git a/src/pages/project/ProjectListView.jsx b/src/pages/project/ProjectListView.jsx deleted file mode 100644 index ad6d4fcb..00000000 --- a/src/pages/project/ProjectListView.jsx +++ /dev/null @@ -1,202 +0,0 @@ -import React, { useState, useEffect } from "react"; -import moment from "moment"; -import { - useProjectDetails, - useProjects, - useUpdateProject, -} from "../../hooks/useProjects"; -import { - getProjectStatusName, - getProjectStatusColor, -} from "../../utils/projectStatus"; -import ProgressBar from "../../components/common/ProgressBar"; -import { useNavigate } from "react-router-dom"; -import ManageProject from "../../components/Project/ManageProject"; -import ProjectRepository from "../../repositories/ProjectRepository"; -import { MANAGE_PROJECT } from "../../utils/constants"; -import { useHasUserPermission } from "../../hooks/useHasUserPermission"; -import ManageProjectInfo from "../../components/Project/ManageProjectInfo"; -import showToast from "../../services/toastService"; -import { getCachedData, cacheData } from "../../slices/apiDataManager"; -import GlobalModel from "../../components/common/GlobalModel"; -import {formatNumber} from "../../utils/dateUtils"; -import { setProjectId } from "../../slices/localVariablesSlice"; -import { useDispatch } from "react-redux"; - -const ProjectListView = ({ projectData, recall }) => { - const [projectInfo, setProjectInfo] = useState(projectData); - const dispatch = useDispatch() - const { projects_Details, loading, error, refetch } = useProjectDetails( - projectInfo?.id,false - ); - const [showModal, setShowModal] = useState(false); - const navigate = useNavigate(); - const ManageProject = useHasUserPermission(MANAGE_PROJECT); - useEffect(() => { - setProjectInfo(projectData); - }, [projectData]); - const { - mutate: updateProject, - isPending, - isSuccess, - isError, -} = useUpdateProject({ - onSuccessCallback: () => { - setShowModal(false); - }, -}) - - const handleShow = async () => { - try { - const { data } = await refetch(); - setShowModal(true); - } catch (err) { - showToast("Failed to load project details", "error"); - } - }; - - const getProgress = (planned, completed) => { - return (completed * 100) / planned + "%"; - }; - const getProgressInNumber = (planned, completed) => { - return (completed * 100) / planned; - }; - - const handleClose = () => setShowModal(false); - - const handleViewProject = () => { - navigate(`/projects/details`); - }; - - const handleFormSubmit = (updatedProject) => { - if (projectInfo?.id) { - updateProject({ - projectId: projectInfo.id, - updatedData: updatedProject, - }); - } - }; - - return ( - <> - {showModal && projects_Details && ( - - )} - -
    - { - dispatch(setProjectId(projectInfo.id)) - navigate(`/projects/details`) - }} - > - {projectInfo.shortName - ? `${projectInfo.name} (${projectInfo.shortName})` - : projectInfo.name} - - {projectInfo.contactPerson} - - {projectInfo.startDate - ? moment(projectInfo.startDate).format("DD-MMM-YYYY") - : "NA"} - - - {projectInfo.endDate - ? moment(projectInfo.endDate).format("DD-MMM-YYYY") - : "NA"} - {formatNumber(projectInfo.plannedWork)} - - -

    - - {getProjectStatusName(projectInfo.projectStatusId)} - -

    -
    -
    - - -
    -