diff --git a/src/components/Activities/AttendcesLogs.jsx b/src/components/Activities/AttendcesLogs.jsx index 312c9237..76e01826 100644 --- a/src/components/Activities/AttendcesLogs.jsx +++ b/src/components/Activities/AttendcesLogs.jsx @@ -4,12 +4,14 @@ import Avatar from "../common/Avatar"; import { convertShortTime } from "../../utils/dateUtils"; import RenderAttendanceStatus from "./RenderAttendanceStatus"; import { useSelector, useDispatch } from "react-redux"; -import { fetchAttendanceData, setAttendanceData } from "../../slices/apiSlice/attedanceLogsSlice"; +import { fetchAttendanceData, setAttendanceData } from "../../slices/apiSlice/attedanceLogsSlice"; // Make sure setAttendanceData is correctly imported import DateRangePicker from "../common/DateRangePicker"; import eventBus from "../../services/eventBus"; +// Custom hook for pagination const usePagination = (data, itemsPerPage) => { const [currentPage, setCurrentPage] = useState(1); + // Ensure data is an array before accessing length const totalItems = Array.isArray(data) ? data.length : 0; const maxPage = Math.ceil(totalItems / itemsPerPage); @@ -28,7 +30,6 @@ const usePagination = (data, itemsPerPage) => { } }, [maxPage]); - // Ensure resetPage is returned by the hook const resetPage = useCallback(() => setCurrentPage(1), []); return { @@ -44,22 +45,29 @@ const AttendanceLog = ({ handleModalData, projectId, setshowOnlyCheckout, - showOnlyCheckout, // Prop for showPending state - searchQuery, // Prop for search query + 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(); - // Get data, loading, and fetching state from the Redux store's attendanceLogs slice const { data: attendanceLogsData, loading: logsLoading, isFetching: logsFetching } = useSelector( - (state) => state.attendanceLogs // Assuming your slice is named 'attendanceLogs' + (state) => state.attendanceLogs ); const [isRefreshing, setIsRefreshing] = useState(false); // Local state for refresh spinner + // Memoize today and yesterday dates to prevent re-creation on every render const today = useMemo(() => { const d = new Date(); d.setHours(0, 0, 0, 0); @@ -69,6 +77,7 @@ const AttendanceLog = ({ 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; }, []); @@ -92,7 +101,7 @@ const AttendanceLog = ({ return nameA.localeCompare(nameB); }, []); - // Effect to fetch attendance data when dateRange or projectId changes + // Effect to fetch attendance data when dateRange or projectId changes, or when refreshed useEffect(() => { const { startDate, endDate } = dateRange; dispatch( @@ -102,21 +111,26 @@ const AttendanceLog = ({ toDate: endDate, }) ); - setIsRefreshing(false); // Reset refreshing state after fetch attempt - }, [dateRange, projectId, dispatch, isRefreshing]); // isRefreshing is a dependency because it triggers a re-fetch + // Reset refreshing state only after the dispatch, assuming fetchAttendanceData + // will eventually update logsLoading/logsFetching + const timer = setTimeout(() => { // Give Redux time to update + setIsRefreshing(false); + }, 500); // Small delay to show spinner for a bit longer + + return () => clearTimeout(timer); + }, [dateRange, projectId, dispatch, isRefreshing]); const processedData = useMemo(() => { - let filteredData = showOnlyCheckout // Use the prop directly - ? attendanceLogsData.filter((item) => item.checkOutTime === null) // Use attendanceLogsData - : attendanceLogsData; // Use attendanceLogsData + let filteredData = showOnlyCheckout + ? (attendanceLogsData || []).filter((item) => item.checkOutTime === null) // Ensure attendanceLogsData is an array + : (attendanceLogsData || []); // Ensure attendanceLogsData is an array // 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 + .filter(Boolean) .join(" ") .toLowerCase(); @@ -128,6 +142,7 @@ const AttendanceLog = ({ }); } + // 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); @@ -158,7 +173,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); @@ -172,46 +190,56 @@ const AttendanceLog = ({ // Create the final sorted array return sortedDates.flatMap((date) => groupedByDate[date]); - }, [attendanceLogsData, showOnlyCheckout, searchQuery, isSameDay, isBeforeToday, sortByName]); // Added attendanceLogsData to dependencies + }, [attendanceLogsData, showOnlyCheckout, searchQuery, isSameDay, isBeforeToday, sortByName]); const { currentPage, totalPages, currentItems: paginatedAttendances, paginate, - resetPage, // Destructure resetPage here + resetPage, } = usePagination(processedData, 20); - // Effect to reset pagination when search query changes + // Effect to reset pagination when search query or showOnlyCheckout changes useEffect(() => { resetPage(); - }, [searchQuery, resetPage]); // Add resetPage to dependencies + }, [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); + const eventDate = (msg.response.checkInTime || msg.response.checkOutTime)?.substring(0, 10); + + // Only refetch if the event relates to the currently viewed project and date range if ( projectId === msg.projectId && - startDate <= checkIn && - checkIn <= endDate + eventDate && + eventDate >= startDate && // Ensure eventDate is within the current range + eventDate <= endDate ) { - const updatedAttendance = data.map((item) => - item.id === msg.response.id - ? { ...item, ...msg.response } - : item + // Trigger a re-fetch of attendance data to get the latest state + dispatch( + fetchAttendanceData({ + projectId, + fromDate: startDate, + toDate: endDate, + }) ); - dispatch(setAttendanceData(updatedAttendance)); // Update Redux store } }, - [projectId, dateRange, data, dispatch] + [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; @@ -233,7 +261,7 @@ const AttendanceLog = ({ const handleRefreshClick = () => { setIsRefreshing(true); // Set refreshing state to true - // The useEffect for fetching data will trigger because isRefreshing is a dependency + // The useEffect for fetching data will automatically trigger due to isRefreshing dependency }; return ( @@ -245,27 +273,27 @@ const AttendanceLog = ({
setshowOnlyCheckout(e.target.checked)} // Use the prop setter + checked={showOnlyCheckout} + onChange={(e) => setshowOnlyCheckout(e.target.checked)} />
@@ -273,7 +301,12 @@ const AttendanceLog = ({ className="table-responsive text-nowrap" style={{ minHeight: "200px", display: 'flex' }} > - {processedData && processedData.length > 0 ? ( + {/* Conditional rendering for loading state */} + {(logsLoading || isRefreshing) ? ( +
+ Loading... +
+ ) : processedData && processedData.length > 0 ? ( @@ -292,96 +325,84 @@ const AttendanceLog = ({ - {(logsLoading || isRefreshing) && ( // Use logsLoading and isRefreshing - - - - )} - {!logsLoading && // Use logsLoading - !isRefreshing && // Use isRefreshing - paginatedAttendances.reduce((acc, attendance, index, arr) => { - const currentDate = moment( - attendance.checkInTime || attendance.checkOutTime - ).format("YYYY-MM-DD"); - const previousAttendance = arr[index - 1]; - const previousDate = previousAttendance - ? moment( - previousAttendance.checkInTime || - previousAttendance.checkOutTime - ).format("YYYY-MM-DD") - : null; + {paginatedAttendances.reduce((acc, attendance, index, arr) => { + const currentDate = moment( + attendance.checkInTime || attendance.checkOutTime + ).format("YYYY-MM-DD"); + const previousAttendance = arr[index - 1]; + const previousDate = previousAttendance + ? moment( + previousAttendance.checkInTime || + previousAttendance.checkOutTime + ).format("YYYY-MM-DD") + : null; - if (!previousDate || currentDate !== previousDate) { - acc.push( - - - - ); - } + if (!previousDate || currentDate !== previousDate) { acc.push( - - - - - - + ); - return acc; - }, [])} + } + acc.push( + + + + + + + + ); + return acc; + }, [])}
Loading...
- - {moment(currentDate).format("DD-MM-YYYY")} - -
- - - {moment( - attendance.checkInTime || attendance.checkOutTime - ).format("DD-MMM-YYYY")} - {convertShortTime(attendance.checkInTime)} - {attendance.checkOutTime - ? convertShortTime(attendance.checkOutTime) - : "--"} - - +
+ + {moment(currentDate).format("DD-MM-YYYY")} +
+ + + {moment( + attendance.checkInTime || attendance.checkOutTime + ).format("DD-MMM-YYYY")} + {convertShortTime(attendance.checkInTime)} + {attendance.checkOutTime + ? convertShortTime(attendance.checkOutTime) + : "--"} + + +
) : ( - !logsLoading && // Use logsLoading - !isRefreshing && ( // Use isRefreshing -
- No employee logs. -
- ) +
+ No employee logs. +
)} - {!logsLoading && !isRefreshing && processedData.length > 20 && ( // Use logsLoading and isRefreshing + {!logsLoading && !isRefreshing && processedData.length > 20 && (