diff --git a/src/components/Activities/AttendcesLogs.jsx b/src/components/Activities/AttendcesLogs.jsx index 0a28ffd3..b741c9ad 100644 --- a/src/components/Activities/AttendcesLogs.jsx +++ b/src/components/Activities/AttendcesLogs.jsx @@ -4,7 +4,7 @@ import Avatar from "../common/Avatar"; import { convertShortTime } from "../../utils/dateUtils"; import RenderAttendanceStatus from "./RenderAttendanceStatus"; import { useSelector, useDispatch } from "react-redux"; -import { fetchAttendanceData } from "../../slices/apiSlice/attedanceLogsSlice"; +import { fetchAttendanceData, setAttendanceData } from "../../slices/apiSlice/attedanceLogsSlice"; // Make sure setAttendanceData is correctly imported import DateRangePicker from "../common/DateRangePicker"; import { clearCacheKey, getCachedData } from "../../slices/apiDataManager"; import eventBus from "../../services/eventBus"; @@ -12,16 +12,25 @@ import AttendanceRepository from "../../repositories/AttendanceRepository"; import { useAttendancesLogs } from "../../hooks/useAttendance"; import { queryClient } from "../../layouts/AuthLayout"; +// Custom hook for pagination const usePagination = (data, itemsPerPage) => { const [currentPage, setCurrentPage] = useState(1); - const maxPage = Math.ceil(data.length / itemsPerPage); + // Ensure data is an array before accessing length + const totalItems = Array.isArray(data) ? data.length : 0; + const maxPage = Math.ceil(totalItems / itemsPerPage); + const currentItems = useMemo(() => { const startIndex = (currentPage - 1) * itemsPerPage; const endIndex = startIndex + itemsPerPage; return data.slice(startIndex, endIndex); }, [data, currentPage, itemsPerPage]); - const paginate = useCallback((pageNumber) => setCurrentPage(pageNumber), []); + const paginate = useCallback((pageNumber) => { + if (pageNumber > 0 && pageNumber <= maxPage) { + setCurrentPage(pageNumber); + } + }, [maxPage]); + const resetPage = useCallback(() => setCurrentPage(1), []); return { @@ -35,21 +44,44 @@ const usePagination = (data, itemsPerPage) => { const AttendanceLog = ({ handleModalData, + projectId, + setshowOnlyCheckout, + showOnlyCheckout, + searchQuery, }) => { const selectedProject = useSelector( (store) => store.localVariables.projectId ); - const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" }); + // Initialize date range with sensible defaults, e.g., last 7 days or current day + const defaultEndDate = moment().format("YYYY-MM-DD"); + const defaultStartDate = moment().subtract(6, 'days').format("YYYY-MM-DD"); // Last 7 days including today + + const [dateRange, setDateRange] = useState({ + startDate: defaultStartDate, + endDate: defaultEndDate + }); const dispatch = useDispatch(); const [loading, setLoading] = useState(false); const [showPending,setShowPending] = useState(false) - const [isRefreshing, setIsRefreshing] = useState(false); - const [processedData, setProcessedData] = useState([]); + const { data: attendanceLogsData, loading: logsLoading, isFetching: logsFetching } = useSelector( + (state) => state.attendanceLogs + ); - const today = new Date(); - today.setHours(0, 0, 0, 0); + const [isRefreshing, setIsRefreshing] = useState(false); // Local state for refresh spinner + const today = useMemo(() => { + const d = new Date(); + d.setHours(0, 0, 0, 0); + return d; + }, []); + + const yesterday = useMemo(() => { + const d = new Date(); + d.setDate(d.getDate() - 1); + d.setHours(0, 0, 0, 0); // Set to start of day for accurate comparison + return d; + }, []); const yesterday = new Date(); yesterday.setDate(yesterday.getDate() - 1); @@ -67,28 +99,49 @@ const AttendanceLog = ({ return d.getTime() < today.getTime(); }; - const sortByName = (a, b) => { - const nameA = a.firstName.toLowerCase() + a.lastName.toLowerCase(); - const nameB = b.firstName.toLowerCase() + b.lastName.toLowerCase(); - return nameA?.localeCompare(nameB); - }; + const sortByName = useCallback((a, b) => { + const nameA = `${a.firstName || ""} ${a.lastName || ""}`.toLowerCase(); + const nameB = `${b.firstName || ""} ${b.lastName || ""}`.toLowerCase(); + return nameA.localeCompare(nameB); + }, []); - const { - data = [], - isLoading, - error, - refetch, - isFetching, - } = useAttendancesLogs( - selectedProject, - dateRange.startDate, - dateRange.endDate - ); - const filtering = (data) => { - const filteredData = showPending - ? data.filter((item) => item.checkOutTime === null) - : data; + // Effect to fetch attendance data when dateRange or projectId changes + useEffect(() => { + const { startDate, endDate } = dateRange; + dispatch( + fetchAttendanceData({ + projectId, + fromDate: startDate, + toDate: endDate, + }) + ); + setIsRefreshing(false); // Reset refreshing state after fetch attempt + }, [dateRange, projectId, dispatch, isRefreshing]); // isRefreshing is a dependency because it triggers a re-fetch + const processedData = useMemo(() => { + let filteredData = showOnlyCheckout // Use the prop directly + ? attendanceLogsData.filter((item) => item.checkOutTime === null) // Use attendanceLogsData + : attendanceLogsData; // Use attendanceLogsData + + // Apply search query filter + if (searchQuery) { + const lowerCaseSearchQuery = searchQuery.toLowerCase(); + filteredData = filteredData.filter((att) => { + // Construct a full name from available parts, filtering out null/undefined + const fullName = [att.firstName, att.middleName, att.lastName] + .filter(Boolean) // This removes null, undefined, or empty string parts + .join(" ") + .toLowerCase(); + + return ( + att.employeeName?.toLowerCase().includes(lowerCaseSearchQuery) || + att.employeeId?.toLowerCase().includes(lowerCaseSearchQuery) || + fullName.includes(lowerCaseSearchQuery) + ); + }); + } + + // Grouping and sorting logic remains mostly the same, ensuring 'filteredData' is used const group1 = filteredData .filter((d) => d.activity === 1 && isSameDay(d.checkInTime)) .sort(sortByName); @@ -119,7 +172,10 @@ const AttendanceLog = ({ // Group by date const groupedByDate = sortedList.reduce((acc, item) => { - const date = (item.checkInTime || item.checkOutTime)?.split("T")[0]; + // Use checkInTime for activity 1, and checkOutTime for others or if checkInTime is null + const dateString = item.activity === 1 ? item.checkInTime : item.checkOutTime; + const date = dateString ? moment(dateString).format("YYYY-MM-DD") : null; + if (date) { acc[date] = acc[date] || []; acc[date].push(item); @@ -131,13 +187,9 @@ const AttendanceLog = ({ (a, b) => new Date(b) - new Date(a) ); - const finalData = sortedDates.flatMap((date) => groupedByDate[date]); - setProcessedData(finalData); - }; - - useEffect(() => { - filtering(data); - }, [data, showPending]); + // Create the final sorted array + return sortedDates.flatMap((date) => groupedByDate[date]); + }, [attendanceLogsData, showOnlyCheckout, searchQuery, isSameDay, isBeforeToday, sortByName]); const { currentPage, @@ -147,40 +199,46 @@ const AttendanceLog = ({ resetPage, } = usePagination(processedData, 20); + // Effect to reset pagination when search query or showOnlyCheckout changes useEffect(() => { resetPage(); - }, [processedData, resetPage]); + }, [searchQuery, showOnlyCheckout, resetPage]); + // Handler for 'attendance_log' event from eventBus + // This will now trigger a re-fetch of data const handler = useCallback( (msg) => { + // Check if the event is relevant to the current project and date range const { startDate, endDate } = dateRange; - const checkIn = msg.response.checkInTime.substring(0, 10); - if ( - selectedProject === msg.projectId && - startDate <= checkIn && - checkIn <= endDate - ) { - queryClient.setQueriesData(["attendanceLogs"],(oldData)=>{ - if(!oldData) { - queryClient.invalidateQueries({queryKey:["attendanceLogs"]}) - } - return oldData.map((record) => - record.id === msg.response.id ? { ...record, ...msg.response } : record - ); - }) + const eventDate = (msg.response.checkInTime || msg.response.checkOutTime)?.substring(0, 10); - filtering(updatedAttendance); - resetPage(); + // Only refetch if the event relates to the currently viewed project and date range + if ( + projectId === msg.projectId && + eventDate && + eventDate >= startDate && // Ensure eventDate is within the current range + eventDate <= endDate + ) { + // Trigger a re-fetch of attendance data to get the latest state + dispatch( + fetchAttendanceData({ + projectId, + fromDate: startDate, + toDate: endDate, + }) + ); } }, - [selectedProject, dateRange, data, filtering, resetPage] + [projectId, dateRange, dispatch] ); + useEffect(() => { eventBus.on("attendance_log", handler); return () => eventBus.off("attendance_log", handler); }, [handler]); + // Handler for 'employee' event from eventBus (already triggers a refetch) const employeeHandler = useCallback( (msg) => { const { startDate, endDate } = dateRange; @@ -204,6 +262,11 @@ const AttendanceLog = ({ return () => eventBus.off("employee", employeeHandler); }, [employeeHandler]); + const handleRefreshClick = () => { + setIsRefreshing(true); // Set refreshing state to true + // The useEffect for fetching data will automatically trigger due to isRefreshing dependency + }; + return ( <>
setShowPending(e.target.checked)} + checked={showOnlyCheckout} + onChange={(e) => setshowOnlyCheckout(e.target.checked)} />
refetch()} + onClick={handleRefreshClick} />
-
- {isLoading ? ( -

Loading...

- ) : data?.length > 0 ? ( +
+ {/* Conditional rendering for loading state */} + {(logsLoading || isRefreshing) ? ( +
+ Loading... +
+ ) : processedData && processedData.length > 0 ? ( @@ -267,9 +335,9 @@ const AttendanceLog = ({ 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) { @@ -287,7 +355,7 @@ const AttendanceLog = ({ ); } acc.push( - +
) : ( -
No Record Available !
+
+ No employee logs. +
)}
- {paginatedAttendances?.length == 0 && data?.length > 0 && ( -
No Pending Record Available !
- )} - {processedData.length > 10 && ( + {!logsLoading && !isRefreshing && processedData.length > 20 && (