fixed attendance - check In -out and persisted date range from Redux store for attendance logs

This commit is contained in:
pramod.mahajan 2025-10-07 12:17:27 +05:30
parent ad1bef4f7b
commit 8bcfcc5718
7 changed files with 188 additions and 147 deletions

View File

@ -12,7 +12,7 @@ import { useQueryClient } from "@tanstack/react-query";
import eventBus from "../../services/eventBus";
import { useSelectedProject } from "../../slices/apiDataManager";
const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizationId, includeInactive, date }) => {
const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizationId, }) => {
const queryClient = useQueryClient();
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
@ -24,7 +24,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat
loading: attLoading,
recall: attrecall,
isFetching
} = useAttendance(selectedProject, organizationId, includeInactive, date);
} = useAttendance(selectedProject, organizationId);
const filteredAttendance = ShowPending
? attendance?.filter(
(att) => att?.checkInTime !== null && att?.checkOutTime === null

View File

@ -5,7 +5,11 @@ import { convertShortTime } from "../../utils/dateUtils";
import RenderAttendanceStatus from "./RenderAttendanceStatus";
import { useSelector, useDispatch } from "react-redux";
import DateRangePicker from "../common/DateRangePicker";
import { clearCacheKey, getCachedData, useSelectedProject } from "../../slices/apiDataManager";
import {
clearCacheKey,
getCachedData,
useSelectedProject,
} from "../../slices/apiDataManager";
import eventBus from "../../services/eventBus";
import AttendanceRepository from "../../repositories/AttendanceRepository";
import { useAttendancesLogs } from "../../hooks/useAttendance";
@ -33,7 +37,7 @@ const usePagination = (data, itemsPerPage) => {
};
};
const AttendanceLog = ({ handleModalData, searchTerm ,organizationId}) => {
const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
// const selectedProject = useSelector(
// (store) => store.localVariables.projectId
// );
@ -41,7 +45,7 @@ const AttendanceLog = ({ handleModalData, searchTerm ,organizationId}) => {
const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" });
const dispatch = useDispatch();
const [loading, setLoading] = useState(false);
const [showPending, setShowPending] = useState(false)
const [showPending, setShowPending] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false);
const [processedData, setProcessedData] = useState([]);
@ -151,33 +155,6 @@ const AttendanceLog = ({ handleModalData, searchTerm ,organizationId}) => {
});
}, [processedData, searchTerm]);
// const filteredSearchData = useMemo(() => {
// let tempData = processedData;
// if (searchTerm) {
// const lowercasedSearchTerm = searchTerm.toLowerCase();
// tempData = tempData.filter((item) => {
// const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
// return fullName.includes(lowercasedSearchTerm);
// });
// }
// if (filters?.selectedOrganization) {
// tempData = tempData.filter(
// (item) => item.organization?.name === filters.selectedOrganization
// );
// }
// if (filters?.selectedServices?.length > 0) {
// tempData = tempData.filter((item) =>
// filters.selectedServices.includes(item.service?.name)
// );
// }
// return tempData;
// }, [processedData, searchTerm, filters]);
const {
currentPage,
totalPages,
@ -235,7 +212,7 @@ const AttendanceLog = ({ handleModalData, searchTerm ,organizationId}) => {
// })
// );
refetch()
refetch();
}
},
[selectedProject, dateRange, data, refetch]
@ -270,11 +247,16 @@ const AttendanceLog = ({ handleModalData, searchTerm ,organizationId}) => {
<label className="form-check-label ms-0">Show Pending</label>
</div>
</div>
</div>
<div className="table-responsive text-nowrap" style={{ minHeight: "200px" }}>
<div
className="table-responsive text-nowrap"
style={{ minHeight: "200px" }}
>
{isLoading ? (
<div className="d-flex justify-content-center align-items-center" style={{ height: "200px" }}>
<div
className="d-flex justify-content-center align-items-center"
style={{ height: "200px" }}
>
<p className="text-secondary">Loading...</p>
</div>
) : filteredSearchData?.length > 0 ? (
@ -287,7 +269,8 @@ const AttendanceLog = ({ handleModalData, searchTerm ,organizationId}) => {
<th className="border-top-1">Date</th>
<th>Organization</th>
<th>
<i className="bx bxs-down-arrow-alt text-success"></i> Check-In
<i className="bx bxs-down-arrow-alt text-success"></i>{" "}
Check-In
</th>
<th>
<i className="bx bxs-up-arrow-alt text-danger"></i> Check-Out
@ -303,9 +286,9 @@ const AttendanceLog = ({ handleModalData, searchTerm ,organizationId}) => {
const previousAttendance = arr[index - 1];
const previousDate = previousAttendance
? moment(
previousAttendance.checkInTime ||
previousAttendance.checkOutTime
).format("YYYY-MM-DD")
previousAttendance.checkInTime ||
previousAttendance.checkOutTime
).format("YYYY-MM-DD")
: null;
if (!previousDate || currentDate !== previousDate) {
@ -366,7 +349,12 @@ const AttendanceLog = ({ handleModalData, searchTerm ,organizationId}) => {
</tbody>
</table>
) : (
<div className="my-12"><span className="text-secondary">No data available for the selected date range. Please Select another date.</span></div>
<div className="my-12">
<span className="text-secondary">
No data available for the selected date range. Please Select
another date.
</span>
</div>
)}
</div>
{paginatedAttendances?.length == 0 && filteredSearchData?.length > 0 && (
@ -392,8 +380,9 @@ const AttendanceLog = ({ handleModalData, searchTerm ,organizationId}) => {
(pageNumber) => (
<li
key={pageNumber}
className={`page-item ${currentPage === pageNumber ? "active" : ""
}`}
className={`page-item ${
currentPage === pageNumber ? "active" : ""
}`}
>
<button
className="page-link"
@ -405,8 +394,9 @@ const AttendanceLog = ({ handleModalData, searchTerm ,organizationId}) => {
)
)}
<li
className={`page-item ${currentPage === totalPages ? "disabled" : ""
}`}
className={`page-item ${
currentPage === totalPages ? "disabled" : ""
}`}
>
<button
className="page-link"

View File

@ -96,12 +96,12 @@ const CheckInCheckOut = ({ modeldata, closeModal, handleSubmitForm }) => {
};
return (
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
<div className="col-12 d-flex justify-content-center">
<label className="fs-5 text-dark text-center">
<form className="row p-2" onSubmit={handleSubmit(onSubmit)}>
<div className="col-12 d-flex justify-content-center mb-4">
<label className="fs-5 tex-semibold text-center">
{modeldata?.checkInTime && !modeldata?.checkOutTime
? "Check-out :"
: "Check-in :"}
? "Check-Out "
: "Check-In "}
</label>
</div>

View File

@ -1,5 +1,7 @@
import React, { useEffect, useRef } from "react";
import { useController, useFormContext, useWatch } from "react-hook-form";
import { useSelector } from "react-redux";
const DateRangePicker = ({
md,
sm,
@ -9,18 +11,28 @@ const DateRangePicker = ({
}) => {
const inputRef = useRef(null);
const persistedRange = useSelector(
(store) => store.localVariables.attendance.defaultDateRange
);
useEffect(() => {
const endDate = new Date();
if (endDateMode === "yesterday") {
endDate.setDate(endDate.getDate() - 1);
let startDate, endDate;
if (persistedRange?.startDate && persistedRange?.endDate) {
startDate = new Date(persistedRange.startDate);
endDate = new Date(persistedRange.endDate);
} else {
endDate = new Date();
if (endDateMode === "yesterday") {
endDate.setDate(endDate.getDate() - 1);
}
endDate.setHours(0, 0, 0, 0);
startDate = new Date(endDate);
startDate.setDate(endDate.getDate() - (DateDifference - 1));
startDate.setHours(0, 0, 0, 0);
}
endDate.setHours(0, 0, 0, 0);
const startDate = new Date(endDate);
startDate.setDate(endDate.getDate() - (DateDifference - 1));
startDate.setHours(0, 0, 0, 0);
const fp = flatpickr(inputRef.current, {
mode: "range",
dateFormat: "Y-m-d",
@ -41,14 +53,12 @@ const DateRangePicker = ({
endDate: endDate.toLocaleDateString("en-CA"),
});
return () => {
fp.destroy();
};
}, [onRangeChange, DateDifference, endDateMode]);
return () => fp.destroy();
}, [onRangeChange, DateDifference, endDateMode, persistedRange]);
const handleIconClick = () => {
if (inputRef.current) {
inputRef.current._flatpickr.open(); // directly opens flatpickr
if (inputRef.current?._flatpickr) {
inputRef.current._flatpickr.open();
}
};
@ -63,9 +73,10 @@ const DateRangePicker = ({
/>
<i
className="bx bx-calendar calendar-icon cursor-pointer position-relative top-50 translate-middle-y " onClick={handleIconClick}
className="bx bx-calendar calendar-icon cursor-pointer position-relative top-50 translate-middle-y"
onClick={handleIconClick}
style={{ right: "22px", bottom: "-8px" }}
></i>
/>
</div>
);
};
@ -76,6 +87,7 @@ export default DateRangePicker;
export const DateRangePicker1 = ({
startField = "startDate",
endField = "endDate",

View File

@ -1,5 +1,9 @@
import { useEffect, useState } from "react";
import { cacheData, getCachedData, useSelectedProject } from "../slices/apiDataManager";
import {
cacheData,
getCachedData,
useSelectedProject,
} from "../slices/apiDataManager";
import AttendanceRepository from "../repositories/AttendanceRepository";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import showToast from "../services/toastService";
@ -7,11 +11,8 @@ import { useDispatch, useSelector } from "react-redux";
import { store } from "../store/store";
import { setDefaultDateRange } from "../slices/localVariablesSlice";
// ----------------------------Query-----------------------------
// export const useAttendance = (projectId) => {
// const dispatch = useDispatch()
// const {
@ -41,7 +42,10 @@ import { setDefaultDateRange } from "../slices/localVariablesSlice";
// };
// };
export const useAttendance = (projectId, organizationId, includeInactive = false, date = null) => {
export const useAttendance = (
projectId,
organizationId,
) => {
const dispatch = useDispatch();
const {
@ -51,13 +55,11 @@ export const useAttendance = (projectId, organizationId, includeInactive = false
refetch: recall,
isFetching,
} = useQuery({
queryKey: ["attendance", projectId, organizationId, includeInactive, date], // include filters in cache key
queryKey: ["attendance", projectId, organizationId], // include filters in cache key
queryFn: async () => {
const response = await AttendanceRepository.getAttendance(
projectId,
organizationId,
includeInactive,
date
);
return response.data;
},
@ -70,12 +72,17 @@ export const useAttendance = (projectId, organizationId, includeInactive = false
return { attendance, loading, error, recall, isFetching };
};
export const useAttendancesLogs = (projectId, fromDate, toDate,organizationId) => {
export const useAttendancesLogs = (
projectId,
fromDate,
toDate,
organizationId
) => {
const dispatch = useDispatch();
const enabled = !!projectId && !!fromDate && !!toDate;
const query = useQuery({
queryKey: ['attendanceLogs', projectId, fromDate, toDate,organizationId],
queryKey: ["attendanceLogs", projectId, fromDate, toDate, organizationId],
queryFn: async () => {
const res = await AttendanceRepository.getAttendanceFilteredByDate(
projectId,
@ -101,8 +108,6 @@ export const useAttendancesLogs = (projectId, fromDate, toDate,organizationId) =
return query;
};
export const useEmployeeAttendacesLog = (id) => {
const {
data: logs = [],
@ -117,7 +122,10 @@ export const useEmployeeAttendacesLog = (id) => {
},
enabled: !!id,
onError: (error) => {
showToast(error.message || "Error while fetching Attendance Logs", "error");
showToast(
error.message || "Error while fetching Attendance Logs",
"error"
);
},
});
@ -135,16 +143,22 @@ export const useAttendanceByEmployee = (employeeId, fromDate, toDate) => {
return useQuery({
queryKey: ["employeeAttendance", employeeId, fromDate, toDate],
queryFn: async () => {
const res = await AttendanceRepository.getAttendanceByEmployee(employeeId, fromDate, toDate);
const res = await AttendanceRepository.getAttendanceByEmployee(
employeeId,
fromDate,
toDate
);
return res.data;
},
enabled
enabled,
});
};
export const useRegularizationRequests = (projectId, organizationId, IncludeInActive = false) => {
export const useRegularizationRequests = (
projectId,
organizationId,
IncludeInActive = false
) => {
const dispatch = useDispatch();
const {
@ -159,7 +173,7 @@ export const useRegularizationRequests = (projectId, organizationId, IncludeInAc
const response = await AttendanceRepository.getRegularizeList(
projectId,
organizationId,
IncludeInActive,
IncludeInActive
);
return response.data;
},
@ -172,48 +186,61 @@ export const useRegularizationRequests = (projectId, organizationId, IncludeInAc
return { regularizes, loading, error, recall, isFetching };
};
// -------------------Mutation--------------------------------------
export const useMarkAttendance = () => {
const queryClient = useQueryClient();
const selectedProject = useSelectedProject();
const selectedDateRange = useSelector((store)=>store.localVariables.defaultDateRange)
const selectedDateRange = useSelector(
(store) => store.localVariables.attendance.defaultDateRange
);
const selectedOrganization = useSelector(
(store) => store.localVariables.attendance.SelectedOrg
);
return useMutation({
mutationFn: async ({payload,forWhichTab}) => {
mutationFn: async ({ payload, forWhichTab }) => {
const res = await AttendanceRepository.markAttendance(payload);
return res.data;
},
onSuccess: (data,variables) => {
if(variables.forWhichTab == 1){
queryClient.setQueryData(["attendance",selectedProject], (oldData) => {
if (!oldData) return oldData;
return oldData.map((emp) =>
emp.employeeId === data.employeeId ? { ...emp, ...data } : emp
);
});
}else if(variables.forWhichTab == 2){
// queryClient.invalidateQueries({
// queryKey: ["attendanceLogs"],
// });
queryClient.setQueryData(["attendanceLogs",selectedProject,selectedDateRange.startDate,selectedDateRange.endDate], (oldData) => {
if (!oldData) return oldData;
return oldData.map((record) =>
record.id === data.id ? { ...record, ...data } : record
);
});
queryClient.invalidateQueries({queryKey:["regularizedList"]})
}else(
queryClient.setQueryData(["regularizedList",selectedProject], (oldData) => {
if (!oldData) return oldData;
return oldData.filter((record) => record.id !== data.id)
}),
queryClient.invalidateQueries({queryKey:["attendanceLogs"]})
)
onSuccess: (data, variables) => {
if (variables.forWhichTab == 1) {
queryClient.setQueryData(["attendance", selectedProject,selectedOrganization], (oldData) => {
if(variables.forWhichTab !== 3) showToast("Attendance marked successfully", "success");
if (!oldData) return oldData;
return oldData.map((emp) =>
emp.employeeId === data.employeeId ? { ...emp, ...data } : emp
);
});
} else if (variables.forWhichTab == 2) {
queryClient.setQueryData(
[
"attendanceLogs",
selectedProject,
selectedDateRange.startDate,
selectedDateRange.endDate,selectedOrganization
],
(oldData) => {
if (!oldData) return oldData;
return oldData.map((record) =>
record.id === data.id ? { ...record, ...data } : record
);
}
);
queryClient.invalidateQueries({ queryKey: ["regularizedList"] });
} else
queryClient.setQueryData(
["regularizedList", selectedProject],
(oldData) => {
if (!oldData) return oldData;
return oldData.filter((record) => record.id !== data.id);
}
),
queryClient.invalidateQueries({ queryKey: ["attendanceLogs"] });
if (variables.forWhichTab !== 3)
showToast("Attendance marked successfully", "success");
},
onError: (error) => {
showToast(error.message || "Failed to mark attendance", "error");

View File

@ -16,7 +16,10 @@ import { setProjectId } from "../../slices/localVariablesSlice";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { REGULARIZE_ATTENDANCE } from "../../utils/constants";
import eventBus from "../../services/eventBus";
import { useProjectAssignedOrganizations, 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";
@ -101,11 +104,11 @@ const AttendancePage = () => {
{(modelConfig?.action === 0 ||
modelConfig?.action === 1 ||
modelConfig?.action === 2) && (
<CheckCheckOutmodel
modeldata={modelConfig}
closeModal={closeModal}
/>
)}
<CheckCheckOutmodel
modeldata={modelConfig}
closeModal={closeModal}
/>
)}
{/* For view logs */}
{modelConfig?.action === 6 && (
<AttendLogs Id={modelConfig?.id} closeModal={closeModal} />
@ -134,8 +137,9 @@ const AttendancePage = () => {
<li className="nav-item">
<button
type="button"
className={`nav-link ${activeTab === "all" ? "active" : ""
} fs-6`}
className={`nav-link ${
activeTab === "all" ? "active" : ""
} fs-6`}
onClick={() => handleTabChange("all")}
data-bs-toggle="tab"
data-bs-target="#navs-top-home"
@ -146,8 +150,9 @@ const AttendancePage = () => {
<li className="nav-item">
<button
type="button"
className={`nav-link ${activeTab === "logs" ? "active" : ""
} fs-6`}
className={`nav-link ${
activeTab === "logs" ? "active" : ""
} fs-6`}
onClick={() => handleTabChange("logs")}
data-bs-toggle="tab"
data-bs-target="#navs-top-profile"
@ -159,8 +164,9 @@ const AttendancePage = () => {
<li className={`nav-item ${!DoRegularized ? "d-none" : ""}`}>
<button
type="button"
className={`nav-link ${activeTab === "regularization" ? "active" : ""
} fs-6`}
className={`nav-link ${
activeTab === "regularization" ? "active" : ""
} fs-6`}
onClick={() => handleTabChange("regularization")}
data-bs-toggle="tab"
data-bs-target="#navs-top-messages"
@ -187,8 +193,8 @@ const AttendancePage = () => {
disabled={orgLoading}
>
<option value="">All Organizations</option>
{organizations?.map((org) => (
<option key={org.id} value={org.id}>
{organizations?.map((org, ind) => (
<option key={`${org.id}-${ind}`} value={org.id}>
{org.name}
</option>
))}
@ -204,8 +210,6 @@ const AttendancePage = () => {
style={{ minWidth: "200px" }}
/>
</div>
</div>
</div>

View File

@ -4,10 +4,11 @@ const localVariablesSlice = createSlice({
name: "localVariables",
initialState: {
selectedMaster: "Application Role",
regularizationCount: 0,
defaultDateRange: {
startDate: null,
endDate: null,
// Attendances
attendance: {
regularizationCount: 0,
defaultDateRange: { startDate: null, endDate: null },
SelectedOrg:null,
},
projectId: null,
reload: false,
@ -28,9 +29,20 @@ const localVariablesSlice = createSlice({
changeMaster: (state, action) => {
state.selectedMaster = action.payload;
},
// ATTENDANCE
updateRegularizationCount: (state, action) => {
state.regularizationCount = action.payload;
state.attendance.regularizationCount = action.payload;
},
setDefaultDateRange: (state, action) => {
state.attendance.defaultDateRange = action.payload;
},
setOrganization:(state,action)=>{
state.attendance.SelectedOrg = action.payload;
},
setProjectId: (state, action) => {
localStorage.setItem("project", null);
state.projectId = action.payload;
@ -39,10 +51,6 @@ const localVariablesSlice = createSlice({
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;
@ -83,6 +91,6 @@ export const {
closeOrgModal,
toggleOrgModal,
openAuthModal,
closeAuthModal,
closeAuthModal,setOrganization
} = localVariablesSlice.actions;
export default localVariablesSlice.reducer;