From 2aae7194b72213f99d9796ba3dcd4b6d94114043 Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Wed, 1 Oct 2025 13:00:38 +0530 Subject: [PATCH 1/5] implemented singlR --- .../Documents/DocumentFilterPanel.jsx | 4 +- src/components/Documents/DocumentSkeleton.jsx | 88 +- src/components/Documents/Documents.jsx | 2 +- src/components/Documents/DocumentsList.jsx | 12 +- src/components/Employee/EmpAttendance.jsx | 2 +- src/components/Employee/EmpDashboard.jsx | 8 +- src/components/Employee/EmployeeNav.jsx | 2 +- src/components/Layout/Header.jsx | 31 +- src/components/Project/ProjectCard.jsx | 8 +- src/components/Project/ProjectCardView.jsx | 108 ++- src/components/Project/ProjectListView.jsx | 27 +- src/hooks/useDocument.js | 5 +- src/pages/Directory/DirectoryPage.jsx | 20 +- src/pages/Directory/NoteFilterPanel.jsx | 4 +- src/pages/employee/EmployeeList.jsx | 768 +++++++++--------- src/pages/master/MasterPage.jsx | 5 +- src/pages/master/MasterTable.jsx | 6 +- src/services/signalRService.js | 186 ++--- 18 files changed, 628 insertions(+), 658 deletions(-) diff --git a/src/components/Documents/DocumentFilterPanel.jsx b/src/components/Documents/DocumentFilterPanel.jsx index 15a2cbf1..fdb6a3eb 100644 --- a/src/components/Documents/DocumentFilterPanel.jsx +++ b/src/components/Documents/DocumentFilterPanel.jsx @@ -189,12 +189,12 @@ const DocumentFilterPanel = ({ entityTypeId, onApply }) => {
-
diff --git a/src/components/Documents/DocumentSkeleton.jsx b/src/components/Documents/DocumentSkeleton.jsx index 94198326..6c1f7cb8 100644 --- a/src/components/Documents/DocumentSkeleton.jsx +++ b/src/components/Documents/DocumentSkeleton.jsx @@ -17,54 +17,56 @@ const SkeletonCell = ({ /> ); -export const DocumentTableSkeleton = ({ rows = 5 }) => { +export const DocumentTableSkeleton = ({ rows = 10 }) => { return ( + + + + + + + + + + -
NameDocument TypeUploaded ByUploaded onStatus
- - - - - - - - - + + {[...Array(rows)].map((_, idx) => ( + + {/* Name */} + - - {[...Array(rows)].map((_, idx) => ( - - {/* Name */} - + {/* Document Type */} + - {/* Document Type */} - - - {/* Uploaded By (Avatar + Name) */} - - - {/* Uploaded on */} - + + - {/* Status */} - - - ))} - -
NameDocument TypeUploaded ByUploaded onStatus
+ +
- - + + - - -
- - -
-
+ {/* Uploaded By (Avatar + Name) */} + +
+ -
- -
- + {/* Uploaded on */} + + + + + {/* Status */} + + + + + ))} + + ); }; diff --git a/src/components/Documents/Documents.jsx b/src/components/Documents/Documents.jsx index 8210bf58..bb1569d5 100644 --- a/src/components/Documents/Documents.jsx +++ b/src/components/Documents/Documents.jsx @@ -117,7 +117,7 @@ const Documents = ({ Document_Entity, Entity }) => { }, [Document_Entity]); return ( -
+
{/* Search */} diff --git a/src/components/Documents/DocumentsList.jsx b/src/components/Documents/DocumentsList.jsx index b56060ea..9576099d 100644 --- a/src/components/Documents/DocumentsList.jsx +++ b/src/components/Documents/DocumentsList.jsx @@ -82,9 +82,9 @@ const DocumentsList = ({ if (isLoading || isFetching) return ; if (isError) return
Error: {error?.message || "Something went wrong"}
; - if (isInitialEmpty) return
No documents found yet.
; - if (isSearchEmpty) return
No results found for "{debouncedSearch}"
; - if (isFilterEmpty) return
No documents match your filter.
; + if (isInitialEmpty) return
No documents found yet.
; + if (isSearchEmpty) return
No results found for "{debouncedSearch}"
; + if (isFilterEmpty) return
No documents match your filter.
; const handleDelete = () => { ActiveInActive( @@ -180,10 +180,10 @@ const DocumentsList = ({ /> )} -
+
- - + + {DocumentColumns.map((col) => ( - {updatedColumns.map((col) => ( -
{col.label} diff --git a/src/components/Employee/EmpAttendance.jsx b/src/components/Employee/EmpAttendance.jsx index 548471d8..db58261d 100644 --- a/src/components/Employee/EmpAttendance.jsx +++ b/src/components/Employee/EmpAttendance.jsx @@ -131,7 +131,7 @@ const EmpAttendance = ({ employee }) => { )} -
+
{ return ( <>
-
+
{" "}
-
+ {/*
@@ -29,7 +29,6 @@ const EmpDashboard = ({ profile }) => { className="d-flex mb-4 align-items-start flex-wrap" key={project.id} > - {/* Project Info */}
@@ -70,7 +69,6 @@ const EmpDashboard = ({ profile }) => {
- {/* Dates */}
@@ -79,7 +77,7 @@ const EmpDashboard = ({ profile }) => {
-
+
*/}
); diff --git a/src/components/Employee/EmployeeNav.jsx b/src/components/Employee/EmployeeNav.jsx index 8c25aa22..824949b7 100644 --- a/src/components/Employee/EmployeeNav.jsx +++ b/src/components/Employee/EmployeeNav.jsx @@ -24,7 +24,7 @@ const EmployeeNav = ({ onPillClick, activePill }) => { icon: "bx bx-file", label: "Documents", }, - { key: "activities", icon: "bx bx-grid-alt", label: "Activities" }, + // { key: "activities", icon: "bx bx-grid-alt", label: "Activities" }, ].filter(Boolean); return (
diff --git a/src/components/Layout/Header.jsx b/src/components/Layout/Header.jsx index 25056f77..1aa13c91 100644 --- a/src/components/Layout/Header.jsx +++ b/src/components/Layout/Header.jsx @@ -45,8 +45,9 @@ const Header = () => { pathname ); const isExpensePage = /^\/expenses$/.test(pathname); + const isEmployeePage = /^\/employees$/.test(pathname) - return !(isDirectoryPath || isProfilePage || isExpensePage); + return !(isDirectoryPath || isProfilePage || isExpensePage || isEmployeePage); }; const allowedProjectStatusIds = [ "603e994b-a27f-4e5d-a251-f3d69b0498ba", @@ -83,7 +84,7 @@ const Header = () => { currentProjectDisplayName = projectNames[0].name; } else { if (selectedProject === null) { - currentProjectDisplayName = "All Projects"; + currentProjectDisplayName = projectNames[0].name; } else { const selectedProjectObj = projectNames.find( (p) => p?.id === selectedProject @@ -135,14 +136,14 @@ const Header = () => { const newProjectHandler = useCallback( async (msg) => { - if (HasManageProjectPermission && msg.keyword === "Create_Project") { + if ( msg.keyword === "Create_Project") { await fetchData(); } else if (projectNames?.some((item) => item.id === msg.response.id)) { await fetchData(); } cacheData("hasReceived", false); }, - [HasManageProjectPermission, projectNames, fetchData] + [ projectNames, fetchData] ); useEffect(() => { @@ -212,7 +213,7 @@ const Header = () => { className="dropdown-menu" style={{ overflow: "auto", maxHeight: "300px" }} > - {isDashboardPath && ( + {isProjectPath && (
  • -
  • +
  • + +
  • {/* )} */}
  • {
  • -
    + diff --git a/src/components/Project/ProjectCardView.jsx b/src/components/Project/ProjectCardView.jsx index 1108120c..0d094a35 100644 --- a/src/components/Project/ProjectCardView.jsx +++ b/src/components/Project/ProjectCardView.jsx @@ -1,70 +1,62 @@ -import React from 'react' -import { useProjects } from '../../hooks/useProjects' -import Loader from '../common/Loader' -import ProjectCard from './ProjectCard' - -const ProjectCardView = ({currentItems,setCurrentPage,totalPages }) => { - +import React from "react"; +import { useProjects } from "../../hooks/useProjects"; +import Loader from "../common/Loader"; +import ProjectCard from "./ProjectCard"; +const ProjectCardView = ({ currentItems, setCurrentPage, totalPages }) => { return ( +
    + {currentItems.length === 0 && ( +

    No projects found.

    + )} -
    + {currentItems.map((project) => ( + + ))} - { currentItems.length === 0 && ( -

    No projects found.

    - )} - - {currentItems.map((project) => ( - - ))} - - - { totalPages > 1 && ( -
    - - ) -} + ))} +
  • + +
  • + + + )} +
    + ); +}; -export default ProjectCardView +export default ProjectCardView; diff --git a/src/components/Project/ProjectListView.jsx b/src/components/Project/ProjectListView.jsx index 2e717163..8a21e83c 100644 --- a/src/components/Project/ProjectListView.jsx +++ b/src/components/Project/ProjectListView.jsx @@ -26,9 +26,8 @@ const ProjectListView = ({ const navigate = useNavigate(); const { setMangeProject } = useProjectContext(); // const { data, isLoading, isError, error } = useProjects(); - // check Permissions - const canManageProject = useHasUserPermission(MANAGE_PROJECT); + // const canManageProject = useHasUserPermission(MANAGE_PROJECT); const projectColumns = [ { @@ -125,11 +124,15 @@ const ProjectListView = ({ }, ]; - const handleViewActivities = (project) => { - dispatch(setProjectId(project)); - navigate(`/activities/records?project=${project}`); - }; + // const handleViewActivities = (project) => { + // dispatch(setProjectId(project)); + // navigate(`/activities/records?project=${project}`); + // }; + const handleMoveDetails = (project) => { + dispatch(setProjectId(project)); + navigate("/projects/details"); + }; return (
    @@ -158,11 +161,7 @@ const ProjectListView = ({ : project[col.key] || "N/A"} ))} - diff --git a/src/hooks/useDocument.js b/src/hooks/useDocument.js index 5569d878..92562cd6 100644 --- a/src/hooks/useDocument.js +++ b/src/hooks/useDocument.js @@ -120,7 +120,7 @@ export const useUploadDocument = (onSuccessCallBack) => { DocumentRepository.uploadDocument(DocumentPayload), onSuccess: (data, variables) => { queryClient.invalidateQueries({ queryKey: ["DocumentList"] }); - + queryClient.invalidateQueries({ queryKey: ["Document"] }); if (onSuccessCallBack) onSuccessCallBack(); }, onError: (error) => { @@ -141,7 +141,7 @@ export const useUpdateDocument = (onSuccessCallBack) => { onSuccess: (data, variables) => { const { documentId } = variables; queryClient.invalidateQueries({ queryKey: ["DocumentList"] }); - queryClient.invalidateQueries({ queryKey: ["Document", documentId] }); + queryClient.invalidateQueries({ queryKey: ["Document"] }); if (onSuccessCallBack) onSuccessCallBack(); }, onError: (error) => { @@ -187,6 +187,7 @@ export const useActiveInActiveDocument = ()=>{ onSuccess: (data, variables) => { const {isActive} = variables; queryClient.invalidateQueries({ queryKey: ["DocumentList"] }); + queryClient.invalidateQueries({ queryKey: ["Document"] }); showToast(`Document ${isActive ? "restored":"Deleted"} successfully`,"success") }, onError: (error) => { diff --git a/src/pages/Directory/DirectoryPage.jsx b/src/pages/Directory/DirectoryPage.jsx index ed092e82..744f3460 100644 --- a/src/pages/Directory/DirectoryPage.jsx +++ b/src/pages/Directory/DirectoryPage.jsx @@ -44,7 +44,7 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) { const [searchNote, setSearchNote] = useState(""); const [activeTab, setActiveTab] = useState("notes"); const { setActions } = useFab(); - const [gridView, setGridView] = useState(false); + const [gridView, setGridView] = useState(true); const [isOpenBucket, setOpenBucket] = useState(false); const [isManageContact, setManageContact] = useState({ isOpen: false, @@ -185,14 +185,7 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) { value={searchContact} onChange={(e) => setsearchContact(e.target.value)} /> - + + +
    diff --git a/src/pages/Directory/NoteFilterPanel.jsx b/src/pages/Directory/NoteFilterPanel.jsx index 8a492994..87abd52d 100644 --- a/src/pages/Directory/NoteFilterPanel.jsx +++ b/src/pages/Directory/NoteFilterPanel.jsx @@ -60,12 +60,12 @@ const NoteFilterPanel = ({ onApply, clearFilter }) => {
    -
    diff --git a/src/pages/employee/EmployeeList.jsx b/src/pages/employee/EmployeeList.jsx index 3271af47..14ddf2d5 100644 --- a/src/pages/employee/EmployeeList.jsx +++ b/src/pages/employee/EmployeeList.jsx @@ -51,11 +51,7 @@ const EmployeeList = () => { const Manage_Employee = useHasUserPermission(MANAGE_EMPLOYEES); const { employees, loading, setLoading, error, recallEmployeeData } = - useEmployeesAllOrByProjectId( - showAllEmployees, - null, - showInactive - ); + useEmployeesAllOrByProjectId(showAllEmployees, null, showInactive); const [employeeList, setEmployeeList] = useState([]); const [modelConfig, setModelConfig] = useState(); @@ -204,22 +200,16 @@ const EmployeeList = () => { setCurrentPage((prevPage) => (prevPage !== 1 ? 1 : prevPage)); } - }, [loading, employees, showAllEmployees]); - - + }, [loading, employees, showAllEmployees]); const handler = useCallback( (msg) => { if (employees.some((item) => item.id == msg.employeeId)) { setEmployeeList([]); - recallEmployeeData( - showInactive, - null, - showAllEmployees - ); + recallEmployeeData(showInactive, null, showAllEmployees); } }, - [employees, showInactive, showAllEmployees] + [employees, showInactive, showAllEmployees] ); useEffect(() => { @@ -289,18 +279,16 @@ const EmployeeList = () => { {ViewTeamMember ? ( //
    -
    -
    -
    -
    - {/* Switches: All Employees + Inactive */} -
    - {/* All Employees Switch */} - {/* {ViewAllEmployee && ( +
    +
    +
    + {/* Switches: All Employees + Inactive */} +
    + {/* All Employees Switch */} + {/* {ViewAllEmployee && (
    {
    )} */} - {/* Show Inactive Employees Switch */} - {/* {showAllEmployees && ( */} -
    - setShowInactive(e.target.checked)} - /> - -
    - {/* )} */} -
    - - {/* Right side: Search + Export + Add Employee */} -
    - {/* Search Input - ALWAYS ENABLED */} -
    - -
    - - {/* Export Dropdown */} - - - {/* Add Employee Button */} - {Manage_Employee && ( - - )} + {/* Show Inactive Employees Switch */} + {/* {showAllEmployees && ( */} +
    + setShowInactive(e.target.checked)} + /> +
    + {/* )} */}
    -
    +
      -
    • +
    • handleMoveDetails(project.id)}> Modify
    • -
    • handleViewActivities(project.id)}> + {/*
    • handleViewActivities(project.id)}> Activities -
    • + */}
    - - - - - - + /> + + - - - - - - - {loading && ( - - - - )} - {/* Conditional messages for no data or no search results */} - {!loading && - displayData?.length === 0 && - searchText && - !showAllEmployees ? ( - - - - ) : null} - {!loading && - displayData?.length === 0 && - (!searchText || showAllEmployees) ? ( - - - - ) : null} + Print + + +
  • + handleExport("csv")} + > + CSV + +
  • +
  • + handleExport("excel")} + > + Excel + +
  • +
  • + handleExport("pdf")} + > + PDF + +
  • + + - {/* Render current items */} - {currentItems && - !loading && - currentItems.map((item) => ( - - - - {Manage_Employee && ( - - )} - - ))} - -
    + {/* Search Input - ALWAYS ENABLED */} +
    +
    -
    Email
    -
    -
    Contact
    -
    -
    Designation
    -
    - Joining Date - - Status - - Actions -
    -

    Loading...

    -
    - - '{searchText}' employee not found - {" "} -
    + +
    - + + + + + + + + + + + + + + + + {loading && ( + + + + )} + {/* Conditional messages for no data or no search results */} + {!loading && + displayData?.length === 0 && + searchText ? ( + + + + ) : null} + {!loading && + displayData?.length === 0 && + (!searchText ) ? ( + + + + ) : null} + + {/* Render current items */} + {currentItems && + !loading && + currentItems.map((item) => ( + + + + + + + + + {Manage_Employee && ( + - - - + )} + + ))} + +
    +
    Name
    +
    +
    Email
    +
    +
    Contact
    +
    +
    Designation
    +
    + Joining Date + + Status + + Actions +
    +

    Loading...

    +
    +
    + + '{searchText}' employee not found + {" "} +
    +
    +
    {showInactive ? "No In-active Employeee Found" : "No Employeee Found" }
    +
    + + + {item.email ? ( + + + {item.email} + + ) : ( + - + )} + + + + {item.phoneNumber} + + + + + {item.jobRole || "Not Assign Yet"} + + + {moment(item.joiningDate)?.format("DD-MMM-YYYY")} + + {showInactive ? ( + + Inactive + + ) : ( + + Active + + )} + +
    + +
    + {/* View always visible */} + + + {/* If ACTIVE employee */} + {item.isActive && ( + <> + + + {/* Suspend only when active */} + {item.isActive && ( + + )} + + + + )} + + {/* If INACTIVE employee AND inactive toggle is ON */} + {!item.isActive && showInactive && ( + + )}
    - {item.email ? ( - - - {item.email} - - ) : ( - - - - - )} - - - - {item.phoneNumber} - - - - - {item.jobRole || "Not Assign Yet"} - -
    -
    - {moment(item.joiningDate)?.format("DD-MMM-YYYY")} - - {showInactive ? ( - - Inactive - - ) : ( - - Active - - )} - -
    - -
    - {/* View always visible */} - - - {/* If ACTIVE employee */} - {item.isActive && ( - <> - - - {/* Suspend only when active */} - {item.isActive && ( - - )} - - - - )} - - {/* If INACTIVE employee AND inactive toggle is ON */} - {!item.isActive && showInactive && ( - - )} -
    -
    -
    - - {displayData.length > 0 && ( - - )} -
    + {displayData.length > 0 && ( + + )}
    ) : ( diff --git a/src/pages/master/MasterPage.jsx b/src/pages/master/MasterPage.jsx index a206b913..e10d2e8d 100644 --- a/src/pages/master/MasterPage.jsx +++ b/src/pages/master/MasterPage.jsx @@ -161,8 +161,8 @@ const MasterPage = () => { data={[{ label: "Home", link: "/dashboard" }, { label: "Masters" }]} /> -
    -
    + +
    { handleModalData={handleModalData} />
    -
    diff --git a/src/pages/master/MasterTable.jsx b/src/pages/master/MasterTable.jsx index 8facfdb7..b0e52681 100644 --- a/src/pages/master/MasterTable.jsx +++ b/src/pages/master/MasterTable.jsx @@ -109,11 +109,11 @@ const MasterTable = ({ data, columns, loading, handleModalData }) => { {currentItems.length > 0 ? ( currentItems.map((item, index) => (
    - + +
    + {col.key === "description" ? ( item[col.key] !== undefined && item[col.key] !== null ? ( diff --git a/src/services/signalRService.js b/src/services/signalRService.js index 3d9d40b1..cbf29fd1 100644 --- a/src/services/signalRService.js +++ b/src/services/signalRService.js @@ -32,100 +32,104 @@ export function startSignalR(loggedUser) { .toISOString() .split("T")[0]; connection.on("NotificationEventHandler", (data) => { - if (data.loggedInUserId != loggedUser?.employeeInfo.id) { - // console.log("Notification received:", data); - // if action taken on attendance module - if (data.keyword == "Attendance") { - const checkIn = data.response.checkInTime.substring(0, 10); - if (today === checkIn) { - eventBus.emit("attendance", data); - } - var onlyDate = Number(checkIn.substring(8, 10)); + const { loggedInUserId, keyword, response, employeeList, numberOfImages } = data; + const loggedInId = loggedUser?.employeeInfo?.id; - var afterTwoDay = - checkIn.substring(0, 8) + (onlyDate + 2).toString().padStart(2, "0"); - if ( - afterTwoDay <= today && - (data.response.activity == 4 || data.response.activity == 5) - ) { - eventBus.emit("regularization", data); - } - eventBus.emit("attendance_log", data); - } - if(data.keyword == "Expanse"){ - queryClient.invalidateQueries({queryKey:["Expenses"]}) - } - // if create or update project - if ( - data.keyword == "Create_Project" || - data.keyword == "Update_Project" - ) { - // clearCacheKey("projectslist"); - queryClient.invalidateQueries(['projectslist']); - eventBus.emit("project", data); - } + if (loggedInUserId === loggedInId) return; - // if assign or deassign employee to any project - if (data.keyword == "Assign_Project") { - if ( - data.employeeList.some((item) => item === loggedUser?.employeeInfo.id) - ) { - try { - cacheData("hasReceived", false); - eventBus.emit("assign_project_one", data); - } catch (e) { - // console.error("Error in cacheData:", e); - } - } - eventBus.emit("assign_project_all", data); - } - // if created or updated infra - if (data.keyword == "Infra") { - queryClient.removeQueries({queryKey:["ProjectInfra"]}) - // eventBus.emit("infra", data); - } - if (data.keyword == "Task_Report") { - queryClient.removeQueries({queryKey:["Infra"]}) - // eventBus.emit("infra", data); - } - - if ( data.keyword == "WorkItem" ) - { - queryClient.removeQueries({queryKey:["WorkItems"]}) - } - - // if created or updated Employee - if (data.keyword == "Employee") { - // clearCacheKey("employeeListByProject"); - // clearCacheKey("allEmployeeList"); - // clearCacheKey("allInactiveEmployeeList"); - // clearCacheKey("employeeProfile"); - clearCacheKey("Attendance"); - clearCacheKey("regularizedList") - clearCacheKey("AttendanceLogs") - - // ---we can do also---- - // queryClient.removeQueries(['allEmployee', true]); - // but best practies is refetch - queryClient.invalidateQueries(['allEmployee', true]); - queryClient.invalidateQueries(['allEmployee', false]); - queryClient.invalidateQueries(['employeeProfile', data.response?.employeeId]); - queryClient.invalidateQueries(['employeeListByProject']); // optional if scope - eventBus.emit("employee", data); - } - - if (data.keyword == "Task_Report") { - if(data.numberOfImages > 0){ - eventBus.emit("image_gallery", data); - } - } - if (data.keyword == "Task_Comment") { - if(data.numberOfImages > 0){ - eventBus.emit("image_gallery", data); - } - } + // ---- Handlers for invalidate or remove ---- + const queryInvalidators = { + Expanse: () => { + queryClient.invalidateQueries({ queryKey: ["Expenses"] }), + queryClient.invalidateQueries({ queryKey: ["Expense"] }) + }, + Create_Project: () => queryClient.invalidateQueries(["projectslist"]), + Update_Project: () => queryClient.invalidateQueries(["projectslist"]), + Infra: () => queryClient.removeQueries({ queryKey: ["ProjectInfra"] }), + Task_Report: () => queryClient.invalidateQueries({ queryKey: ["Infra"] }), + WorkItem: () => queryClient.invalidateQueries({ queryKey: ["WorkItems"] }), + Directory_Notes:()=>{ + queryClient.invalidateQueries({queryKey:["directoryNotes"]}) + queryClient.invalidateQueries({queryKey:["Notes"]}) + }, + Directory_Buckets:()=>{ + queryClient.invalidateQueries({queryKey:["bucketList"]}) + }, + Directory:()=>{ + queryClient.invalidateQueries({queryKey:["contacts"]}) + queryClient.invalidateQueries({queryKey:["Contact"]}) + queryClient.invalidateQueries({queryKey:["ContactProfile"]}) } - }); + + }; + + // ---- Keyword based event emitters ---- + const emitters = { + employee: () => eventBus.emit("employee", data), + project: () => eventBus.emit("project", data), + infra: () => eventBus.emit("infra", data), + assign_project_all: () => eventBus.emit("assign_project_all", data), + image_gallery: () => eventBus.emit("image_gallery", data), + }; + + // ---- Handle Attendance ---- + if (keyword === "Attendance") { + const checkIn = response.checkInTime.substring(0, 10); + if (today === checkIn) eventBus.emit("attendance", data); + + const onlyDate = Number(checkIn.substring(8, 10)); + const afterTwoDay = + checkIn.substring(0, 8) + (onlyDate + 2).toString().padStart(2, "0"); + + if ( + afterTwoDay <= today && + (response.activity === 4 || response.activity === 5) + ) { + eventBus.emit("regularization", data); + } + eventBus.emit("attendance_log", data); + } + + // ---- Invalidate/Remove cache by keywords ---- + if (queryInvalidators[keyword]) { + queryInvalidators[keyword](); + } + + // ---- Project creation/update ---- + if (keyword === "Create_Project" || keyword === "Update_Project") { + emitters.project(); + } + + // ---- Assign/deassign project ---- + if (keyword === "Assign_Project") { + if (employeeList?.includes(loggedInId)) { + try { + cacheData("hasReceived", false); + eventBus.emit("assign_project_one", data); + } catch {} + } + emitters.assign_project_all(); + } + + // ---- Employee update ---- + if (keyword === "Employee") { + clearCacheKey("Attendance"); + clearCacheKey("regularizedList"); + clearCacheKey("AttendanceLogs"); + + queryClient.invalidateQueries(["allEmployee", true]); + queryClient.invalidateQueries(["allEmployee", false]); + queryClient.invalidateQueries(["employeeProfile", response?.employeeId]); + queryClient.invalidateQueries(["employeeListByProject"]); + + emitters.employee(); + } + + // ---- Image related events ---- + if (["Task_Report", "Task_Comment"].includes(keyword) && numberOfImages > 0) { + emitters.image_gallery(); + } +}); connection .start(); From 49b597c833ccc09fb2f5338b0673076c5f44ba07 Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Wed, 1 Oct 2025 15:34:19 +0530 Subject: [PATCH 2/5] fixed expense date, added project name at check in checkout modal --- .../Activities/CheckCheckOutForm.jsx | 55 +++--- src/components/Dashboard/AttendanceChart.jsx | 3 +- src/components/Dashboard/Dashboard.jsx | 52 +++--- .../Expenses/ExpenseFilterPanel.jsx | 1 + src/components/Expenses/ExpenseSchema.js | 43 +++-- src/components/Expenses/ViewExpense.jsx | 13 +- src/components/Layout/Header.jsx | 162 ++++++++---------- src/pages/Directory/ContactsPage.jsx | 5 + src/utils/appUtils.js | 9 +- src/utils/constants.jsx | 18 +- 10 files changed, 193 insertions(+), 168 deletions(-) diff --git a/src/components/Activities/CheckCheckOutForm.jsx b/src/components/Activities/CheckCheckOutForm.jsx index 1473f746..ab105d84 100644 --- a/src/components/Activities/CheckCheckOutForm.jsx +++ b/src/components/Activities/CheckCheckOutForm.jsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; @@ -9,6 +9,7 @@ import showToast from "../../services/toastService"; import { checkIfCurrentDate } from "../../utils/dateUtils"; import { useMarkAttendance } from "../../hooks/useAttendance"; import { useSelectedProject } from "../../slices/apiDataManager"; +import { useProjectName } from "../../hooks/useProjects"; const createSchema = (modeldata) => { return z @@ -19,31 +20,36 @@ const createSchema = (modeldata) => { .max(200, "Description should be less than 200 characters") .optional(), }) - .refine((data) => { - if (modeldata?.checkInTime && !modeldata?.checkOutTime) { - const checkIn = new Date(modeldata.checkInTime); - const [time, modifier] = data.markTime.split(" "); - const [hourStr, minuteStr] = time.split(":"); - let hour = parseInt(hourStr, 10); - const minute = parseInt(minuteStr, 10); + .refine( + (data) => { + if (modeldata?.checkInTime && !modeldata?.checkOutTime) { + const checkIn = new Date(modeldata.checkInTime); + const [time, modifier] = data.markTime.split(" "); + const [hourStr, minuteStr] = time.split(":"); + let hour = parseInt(hourStr, 10); + const minute = parseInt(minuteStr, 10); - if (modifier === "PM" && hour !== 12) hour += 12; - if (modifier === "AM" && hour === 12) hour = 0; + if (modifier === "PM" && hour !== 12) hour += 12; + if (modifier === "AM" && hour === 12) hour = 0; - const checkOut = new Date(checkIn); - checkOut.setHours(hour, minute, 0, 0); + const checkOut = new Date(checkIn); + checkOut.setHours(hour, minute, 0, 0); - return checkOut >= checkIn; + return checkOut >= checkIn; + } + return true; + }, + { + message: "Checkout time must be later than check-in time", + path: ["markTime"], } - return true; - }, { - message: "Checkout time must be later than check-in time", - path: ["markTime"], - }); + ); }; const CheckInCheckOut = ({ modeldata, closeModal, handleSubmitForm }) => { + const [currentProject, setCurrentProject] = useState(null); const projectId = useSelectedProject(); + const { projectNames, loading } = useProjectName(); const { mutate: MarkAttendance } = useMarkAttendance(); const [isLoading, setIsLoading] = useState(false); const coords = usePositionTracker(); @@ -95,17 +101,24 @@ const CheckInCheckOut = ({ modeldata, closeModal, handleSubmitForm }) => { closeModal(); }; + useEffect(() => { + if (projectId && projectNames) { + setCurrentProject( + projectNames?.find((project) => project.id === projectId) + ); + } + }, [projectNames, projectId, loading]); + return (
    -
    diff --git a/src/components/Expenses/ExpenseSchema.js b/src/components/Expenses/ExpenseSchema.js index b1339228..786aa5b9 100644 --- a/src/components/Expenses/ExpenseSchema.js +++ b/src/components/Expenses/ExpenseSchema.js @@ -1,4 +1,5 @@ import { z } from "zod"; +import { localToUtc } from "../../utils/appUtils"; const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB const ALLOWED_TYPES = [ @@ -17,15 +18,12 @@ export const ExpenseSchema = (expenseTypes) => { .min(1, { message: "Expense type is required" }), paymentModeId: z.string().min(1, { message: "Payment mode is required" }), paidById: z.string().min(1, { message: "Employee name is required" }), - transactionDate: z - .string() - .min(1, { message: "Date is required" }) - , + transactionDate: z.string().min(1, { message: "Date is required" }), transactionId: z.string().optional(), description: z.string().min(1, { message: "Description is required" }), location: z.string().min(1, { message: "Location is required" }), supplerName: z.string().min(1, { message: "Supplier name is required" }), - gstNumber :z.string().optional(), + gstNumber: z.string().optional(), amount: z.coerce .number({ invalid_type_error: "Amount is required and must be a number", @@ -54,8 +52,6 @@ export const ExpenseSchema = (expenseTypes) => { }) ) .nonempty({ message: "At least one file attachment is required" }), - - }) .refine( (data) => { @@ -68,9 +64,14 @@ export const ExpenseSchema = (expenseTypes) => { path: ["paidById"], } ) - .superRefine((data, ctx) => { - const expenseType = expenseTypes.find((et) => et.id === data.expensesTypeId); - if (expenseType?.noOfPersonsRequired && (!data.noOfPersons || data.noOfPersons < 1)) { + .superRefine((data, ctx) => { + const expenseType = expenseTypes.find( + (et) => et.id === data.expensesTypeId + ); + if ( + expenseType?.noOfPersonsRequired && + (!data.noOfPersons || data.noOfPersons < 1) + ) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "No. of Persons is required and must be at least 1", @@ -92,12 +93,14 @@ export const defaultExpense = { supplerName: "", amount: "", noOfPersons: "", - gstNumber:"", + gstNumber: "", billAttachments: [], }; - -export const ExpenseActionScheam = (isReimbursement = false) => { +export const ExpenseActionScheam = ( + isReimbursement = false, + transactionDate +) => { return z .object({ comment: z.string().min(1, { message: "Please leave comment" }), @@ -122,6 +125,15 @@ export const ExpenseActionScheam = (isReimbursement = false) => { message: "Reimburse Date is required", }); } + // let reimburse_Date = localToUtc(data.reimburseDate); + // if (transactionDate > reimburse_Date) { + // ctx.addIssue({ + // code: z.ZodIssueCode.custom, + // path: ["reimburseDate"], + // message: + // "Reimburse Date must be greater than or equal to Expense created Date", + // }); + // } if (!data.reimburseById) { ctx.addIssue({ code: z.ZodIssueCode.custom, @@ -133,7 +145,7 @@ export const ExpenseActionScheam = (isReimbursement = false) => { }); }; - export const defaultActionValues = { +export const defaultActionValues = { comment: "", statusId: "", @@ -142,8 +154,6 @@ export const ExpenseActionScheam = (isReimbursement = false) => { reimburseById: null, }; - - export const SearchSchema = z.object({ projectIds: z.array(z.string()).optional(), statusIds: z.array(z.string()).optional(), @@ -163,4 +173,3 @@ export const defaultFilter = { startDate: null, endDate: null, }; - diff --git a/src/components/Expenses/ViewExpense.jsx b/src/components/Expenses/ViewExpense.jsx index 8fca0998..7db45afc 100644 --- a/src/components/Expenses/ViewExpense.jsx +++ b/src/components/Expenses/ViewExpense.jsx @@ -9,7 +9,7 @@ import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { defaultActionValues, ExpenseActionScheam } from "./ExpenseSchema"; import { useExpenseContext } from "../../pages/Expense/ExpensePage"; -import { getColorNameFromHex, getIconByFileType } from "../../utils/appUtils"; +import { getColorNameFromHex, getIconByFileType, localToUtc } from "../../utils/appUtils"; import { ExpenseDetailsSkeleton } from "./ExpenseSkeleton"; import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { @@ -38,7 +38,7 @@ const ViewExpense = ({ ExpenseId }) => { const IsReview = useHasUserPermission(REVIEW_EXPENSE); const [imageLoaded, setImageLoaded] = useState({}); const { setDocumentView } = useExpenseContext(); - const ActionSchema = ExpenseActionScheam(IsPaymentProcess) ?? z.object({}); + const ActionSchema = ExpenseActionScheam(IsPaymentProcess,data?.createdAt) ?? z.object({}); const navigate = useNavigate(); const { register, @@ -91,9 +91,7 @@ const ViewExpense = ({ ExpenseId }) => { const onSubmit = (formData) => { const Payload = { ...formData, - reimburseDate: moment - .utc(formData.reimburseDate, "DD-MM-YYYY") - .toISOString(), + reimburseDate:localToUtc(formData.reimburseDate), expenseId: ExpenseId, comment: formData.comment, }; @@ -397,7 +395,8 @@ const ViewExpense = ({ ExpenseId }) => { {errors.reimburseDate && ( @@ -410,7 +409,7 @@ const ViewExpense = ({ ExpenseId }) => { diff --git a/src/components/Layout/Header.jsx b/src/components/Layout/Header.jsx index 1aa13c91..ec55b48c 100644 --- a/src/components/Layout/Header.jsx +++ b/src/components/Layout/Header.jsx @@ -1,3 +1,4 @@ +import { useCallback, useEffect, useState,useMemo } from "react"; import getGreetingMessage from "../../utils/greetingHandler"; import { cacheData, @@ -14,119 +15,103 @@ import { useLocation, useNavigate, useParams } from "react-router-dom"; import Avatar from "../../components/common/Avatar"; import { useChangePassword } from "../Context/ChangePasswordContext"; import { useProjectModal, useProjects } from "../../hooks/useProjects"; -import { useCallback, useEffect, useState } from "react"; import { useProjectName } from "../../hooks/useProjects"; import eventBus from "../../services/eventBus"; import { useHasUserPermission } from "../../hooks/useHasUserPermission"; -import { MANAGE_PROJECT } from "../../utils/constants"; +import { ALLOW_PROJECTSTATUS_ID, MANAGE_PROJECT, UUID_REGEX } from "../../utils/constants"; import { useAuthModal, useLogout } from "../../hooks/useAuth"; const Header = () => { - const { profile } = useProfile(); + const { profile } = useProfile(); + const { data: masterData } = useMaster(); const location = useLocation(); const dispatch = useDispatch(); - const { data, loading } = useMaster(); const navigate = useNavigate(); - const { onOpen } = useAuthModal(); - const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT); - const { mutate: logout, isPending: logouting } = useLogout(); + const { openModal } = useProjectModal(); + const { mutate: logout, isPending: logouting } = useLogout(); + const { onOpen } = useAuthModal(); + const { openChangePassword } = useChangePassword(); + const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT); - const isDashboardPath = - /^\/dashboard$/.test(location.pathname) || /^\/$/.test(location.pathname); - const isProjectPath = /^\/projects$/.test(location.pathname); + const pathname = location.pathname; - const showProjectDropdown = (pathname) => { - const isDirectoryPath = /^\/directory$/.test(pathname); + // ======= MEMO CHECKS ======= + const isDashboardPath = pathname === "/" || pathname === "/dashboard"; + const isProjectPath = pathname === "/projects"; + const isDirectory = pathname === "/directory"; + const isEmployeeList = pathname === "/employees"; + const isExpense = pathname === "/expenses"; + const isEmployeeProfile = UUID_REGEX.test(pathname); - // const isProfilePage = /^\/employee$/.test(location.pathname); - const isProfilePage = - /^\/employee\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test( - pathname - ); - const isExpensePage = /^\/expenses$/.test(pathname); - const isEmployeePage = /^\/employees$/.test(pathname) + const hideDropPaths = + isDirectory || isEmployeeList || isExpense || isEmployeeProfile; - return !(isDirectoryPath || isProfilePage || isExpensePage || isEmployeePage); - }; - const allowedProjectStatusIds = [ - "603e994b-a27f-4e5d-a251-f3d69b0498ba", - "cdad86aa-8a56-4ff4-b633-9c629057dfef", - "b74da4c2-d07e-46f2-9919-e75e49b12731", - ]; - - const getRole = (roles, joRoleId) => { - if (!Array.isArray(roles)) return "User"; - let role = roles.find((role) => role.id === joRoleId); - return role ? role.name : "User"; - }; - - const handleProfilePage = () => { - navigate(`/employee/${profile?.employeeInfo?.id}`); - }; + const showProjectDropdown = !hideDropPaths; + // ===== Project Names & Selected Project ===== const { projectNames, loading: projectLoading, fetchData } = useProjectName(); - const selectedProject = useSelectedProject(); - const projectsForDropdown = isDashboardPath - ? projectNames - : projectNames?.filter((project) => - allowedProjectStatusIds.includes(project.projectStatusId) - ); + const projectsForDropdown = useMemo( + () => + isDashboardPath + ? projectNames + : projectNames?.filter((project) => + ALLOW_PROJECTSTATUS_ID.includes(project.projectStatusId) + ), + [projectNames, isDashboardPath] + ); - let currentProjectDisplayName; - if (projectLoading) { - currentProjectDisplayName = "Loading..."; - } else if (!projectNames || projectNames.length === 0) { - currentProjectDisplayName = "No Projects Assigned"; - } else if (projectNames.length === 1) { - currentProjectDisplayName = projectNames[0].name; - } else { - if (selectedProject === null) { - currentProjectDisplayName = projectNames[0].name; - } else { - const selectedProjectObj = projectNames.find( - (p) => p?.id === selectedProject - ); - currentProjectDisplayName = selectedProjectObj - ? selectedProjectObj.name - : "All Projects"; - } - } + const currentProjectDisplayName = useMemo(() => { + if (projectLoading) return "Loading..."; + if (!projectNames?.length) return "No Projects Assigned"; + if (projectNames.length === 1) return projectNames[0].name; - const { openChangePassword } = useChangePassword(); + const selectedObj = projectNames.find((p) => p.id === selectedProject); + return selectedObj + ? selectedObj.name + : projectNames[0]?.name || "No Projects Assigned"; + }, [projectLoading, projectNames, selectedProject]); + // ===== Role Helper ===== + const getRole = (roles, joRoleId) => { + if (!Array.isArray(roles)) return "User"; + return roles.find((r) => r.id === joRoleId)?.name || "User"; + }; + + // ===== Navigate to Profile ===== + const handleProfilePage = () => + navigate(`/employee/${profile?.employeeInfo?.id}`); + + // ===== Set default project on load ===== useEffect(() => { if ( - projectNames && - projectNames.length > 0 && + projectNames?.length && selectedProject === undefined && !getCachedData("hasReceived") ) { if (projectNames.length === 1) { - dispatch(setProjectId(projectNames[0]?.id || null)); + dispatch(setProjectId(projectNames[0].id || null)); } else { if (isDashboardPath) { dispatch(setProjectId(null)); } else { - const firstAllowedProject = projectNames.find((project) => - allowedProjectStatusIds.includes(project.projectStatusId) + const firstAllowed = projectNames.find((project) => + ALLOW_PROJECTSTATUS_ID.includes(project.projectStatusId) ); - dispatch(setProjectId(firstAllowedProject?.id || null)); + dispatch(setProjectId(firstAllowed?.id || null)); } } } }, [projectNames, selectedProject, dispatch, isDashboardPath]); + // ===== Event Handlers ===== const handler = useCallback( async (data) => { if (!HasManageProjectPermission) { await fetchData(); - const projectExist = data.projectIds.some( - (item) => item === selectedProject - ); - if (projectExist) { + if (data.projectIds?.includes(selectedProject)) { cacheData("hasReceived", false); } } @@ -136,14 +121,15 @@ const Header = () => { const newProjectHandler = useCallback( async (msg) => { - if ( msg.keyword === "Create_Project") { - await fetchData(); - } else if (projectNames?.some((item) => item.id === msg.response.id)) { + if ( + msg.keyword === "Create_Project" || + projectNames?.some((p) => p.id === msg.response?.id) + ) { await fetchData(); + cacheData("hasReceived", false); } - cacheData("hasReceived", false); }, - [ projectNames, fetchData] + [projectNames, fetchData] ); useEffect(() => { @@ -160,10 +146,10 @@ const Header = () => { }; }, [handler, newProjectHandler]); - const handleProjectChange = (project) => { - dispatch(setProjectId(project)); - - if (isProjectPath && project !== null) { + // ===== Project Change ===== + const handleProjectChange = (projectId) => { + dispatch(setProjectId(projectId)); + if (isProjectPath && projectId !== null) { navigate("/projects/details"); } }; @@ -187,7 +173,7 @@ const Header = () => { className="navbar-nav-right d-flex align-items-center justify-content-between" id="navbar-collapse" > - {showProjectDropdown(location.pathname) && ( + {showProjectDropdown && (
    @@ -213,16 +199,6 @@ const Header = () => { className="dropdown-menu" style={{ overflow: "auto", maxHeight: "300px" }} > - {isProjectPath && ( -
  • - -
  • - )} {[...projectsForDropdown] .sort((a, b) => a?.name?.localeCompare(b.name)) .map((project) => ( @@ -290,7 +266,7 @@ const Header = () => { {profile?.employeeInfo?.firstName} - {getRole(data, profile?.employeeInfo?.joRoleId)} + {getRole(masterData, profile?.employeeInfo?.joRoleId)}
    diff --git a/src/pages/Directory/ContactsPage.jsx b/src/pages/Directory/ContactsPage.jsx index 01023229..54e2bdf5 100644 --- a/src/pages/Directory/ContactsPage.jsx +++ b/src/pages/Directory/ContactsPage.jsx @@ -86,6 +86,11 @@ const ContactsPage = ({ projectId, searchText, onExport }) => { )} + + + {data?.data?.length === 0 && (
    + {searchText ? `No contact found for "${searchText}"`:"No contacts found" } +
    )} {data?.data?.map((contact) => (
    { if (bytes < 1024) return bytes + " B"; else if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + " KB"; @@ -72,12 +72,11 @@ export const normalizeAllowedContentTypes = (allowedContentType) => { export function localToUtc(localDateString) { if (!localDateString || typeof localDateString !== "string") return null; + const [year, month, day] = localDateString.trim().split("-"); - if (!year || !month || !day) return null; - - const date = new Date(Number(year), Number(month) - 1, Number(day), 0, 0, 0); + const date = new Date(Date.UTC(Number(year), Number(month) - 1, Number(day), 0, 0, 0)); return isNaN(date.getTime()) ? null : date.toISOString(); -} +} \ No newline at end of file diff --git a/src/utils/constants.jsx b/src/utils/constants.jsx index 62c686c9..8ea2da02 100644 --- a/src/utils/constants.jsx +++ b/src/utils/constants.jsx @@ -1,3 +1,8 @@ +export const BASE_URL = process.env.VITE_BASE_URL; + +// export const BASE_URL = "https://api.marcoaiot.com"; + + export const THRESH_HOLD = 48; // hours export const DURATION_TIME = 10; // minutes export const ITEMS_PER_PAGE = 20; @@ -140,8 +145,17 @@ export const PROJECT_STATUS = [ label: "Completed", }, ]; + + +export const UUID_REGEX = + /^\/employee\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/; + +export const ALLOW_PROJECTSTATUS_ID = [ + "603e994b-a27f-4e5d-a251-f3d69b0498ba", + "cdad86aa-8a56-4ff4-b633-9c629057dfef", + "b74da4c2-d07e-46f2-9919-e75e49b12731", +]; + export const DEFAULT_EMPTY_STATUS_ID = "00000000-0000-0000-0000-000000000000"; -export const BASE_URL = process.env.VITE_BASE_URL; -// export const BASE_URL = "https://api.marcoaiot.com"; From 2531d912091775089e2d54f71e07f9af3368efb2 Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Wed, 1 Oct 2025 16:21:08 +0530 Subject: [PATCH 3/5] fixed dashboard graph --- src/components/Dashboard/AttendanceChart.jsx | 113 ++++++------------- src/components/Dashboard/Dashboard.jsx | 16 +-- src/hooks/useDashboard_Data.jsx | 63 ++++++----- 3 files changed, 67 insertions(+), 125 deletions(-) diff --git a/src/components/Dashboard/AttendanceChart.jsx b/src/components/Dashboard/AttendanceChart.jsx index a4112a49..3b58b897 100644 --- a/src/components/Dashboard/AttendanceChart.jsx +++ b/src/components/Dashboard/AttendanceChart.jsx @@ -4,6 +4,7 @@ import ReactApexChart from "react-apexcharts"; import { useAttendanceOverviewData } from "../../hooks/useDashboard_Data"; import flatColors from "../Charts/flatColor"; import ChartSkeleton from "../Charts/Skelton"; +import { useSelectedProject } from "../../slices/apiDataManager"; const formatDate = (dateStr) => { const date = new Date(dateStr); @@ -13,16 +14,20 @@ const formatDate = (dateStr) => { }); }; -const AttendanceOverview = ({projectId}) => { +const AttendanceOverview = () => { const [dayRange, setDayRange] = useState(7); const [view, setView] = useState("chart"); - - const { attendanceOverviewData, loading, error } = useAttendanceOverviewData( - projectId, + const selectedProject = useSelectedProject() + const { data: attendanceOverviewData, isLoading, isError, error } = useAttendanceOverviewData( + selectedProject, dayRange ); const { tableData, roles, dates } = useMemo(() => { + if (!attendanceOverviewData || attendanceOverviewData.length === 0) { + return { tableData: [], roles: [], dates: [] }; + } + const map = new Map(); attendanceOverviewData.forEach((entry) => { @@ -35,7 +40,8 @@ const AttendanceOverview = ({projectId}) => { ...new Set(attendanceOverviewData.map((e) => e.role.trim())), ]; const sortedDates = [...map.keys()]; - const data = sortedDates.map((date) => { + + const tableData = sortedDates.map((date) => { const row = { date }; uniqueRoles.forEach((role) => { row[role] = map.get(date)?.[role] ?? 0; @@ -43,12 +49,8 @@ const AttendanceOverview = ({projectId}) => { return row; }); - return { - tableData: data, - roles: uniqueRoles, - dates: sortedDates, - }; - }, [attendanceOverviewData]); + return { tableData, roles: uniqueRoles, dates: sortedDates }; + }, [attendanceOverviewData,isLoading,selectedProject,dayRange]); const chartSeries = roles.map((role) => ({ name: role, @@ -62,41 +64,21 @@ const AttendanceOverview = ({projectId}) => { height: 400, toolbar: { show: false }, }, - plotOptions: { - bar: { - borderRadius: 2, - columnWidth: "60%", - }, - }, - xaxis: { - categories: tableData.map((row) => row.date), - }, + plotOptions: { bar: { borderRadius: 2, columnWidth: "60%" } }, + xaxis: { categories: tableData.map((row) => row.date) }, yaxis: { show: true, - axisBorder: { - show: true, - color: "#78909C", - offsetX: 0, - offsetY: 0, - }, - axisTicks: { - show: true, - borderType: "solid", - color: "#78909C", - width: 6, - offsetX: 0, - offsetY: 0, - }, - }, - legend: { - position: "bottom", - }, - fill: { - opacity: 1, + axisBorder: { show: true, color: "#78909C" }, + axisTicks: { show: true, color: "#78909C", width: 6 }, }, + legend: { position: "bottom" }, + fill: { opacity: 1 }, colors: roles.map((_, i) => flatColors[i % flatColors.length]), }; + if (isLoading) return
    Loading...
    ; + if (isError) return

    {error.message}

    ; + return (
    {/* Header */} @@ -116,18 +98,14 @@ const AttendanceOverview = ({projectId}) => {