import { useEffect, useState } from "react"; import { useSession } from "../../context/SessionContext"; import AttendanceModal from "./AttendanceModal"; import "../../../Styles/components/attendance/attendance.css"; import { Maximize } from "lucide-react"; import Swal from "sweetalert2"; import { confirmHoldToDelete } from "../utils/confirmHoldToDelete"; import { Tooltip } from "react-tooltip"; import BASE_URL from "@backend/server/config"; import Breadcrumbs from "../breadcrumbs/Breadcrumbs"; import { ChevronDown, ChevronUp } from "lucide-react"; // install lucide-react if not import usePermissions from "../../users/hooks/usePermissions"; const Attendance = () => { const { user } = useSession(); // Get user from context const { permissions, loading: permLoading } = usePermissions(user?.username); const [attendanceData, setAttendanceData] = useState([]); const [loading, setLoading] = useState(true); const [modalVisible, setModalVisible] = useState(false); const [modalData, setModalData] = useState(null); const [searchQuery, setSearchQuery] = useState(""); // New state for search query const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]); // State for selected date const [dateFrom, setDateFrom] = useState(""); // State for "Date From" const [dateTo, setDateTo] = useState(""); // State for "Date To" const isAttendanceSummary = location.pathname.includes === '/attendance'; const [showPresentSummary, setShowPresentSummary] = useState(true); const [showAwaySummary, setShowAwaySummary] = useState(false); const fetchAttendance = async () => { setLoading(true); try { const response = await fetch(`${BASE_URL}/api/attendance/attendance`); const data = await response.json(); if (data.success) { setAttendanceData(data.data); } else { Swal.fire({ icon: "error", title: "Oops!", text: data.message || "Failed to fetch attendance data.", }); } } catch (error) { Swal.fire({ icon: "error", title: "Error!", text: "Error fetching attendance: " + error.message, }); } finally { setLoading(false); } }; // Automatically expand on larger screens useEffect(() => { const handleResize = () => { if (window.innerWidth >= 768) { setShowPresentSummary(true); setShowAwaySummary(true); } else { setShowPresentSummary(false); setShowAwaySummary(false); } }; handleResize(); window.addEventListener("resize", handleResize); return () => window.removeEventListener("resize", handleResize); }, []); useEffect(() => { fetchAttendance(); }, []); const isMidnight = (time) => { return time === "12:00 AM" || time === "00:00:00"; }; const getTodayDate = () => { return new Date().toISOString().split('T')[0]; }; // useEffect(() => { // // Set dateFrom and dateTo to today's date when the component mounts // const today = getTodayDate(); // setDateFrom(today); // setDateTo(today); // fetchAttendance(); // }, []); // const showToday = () => { // const today = getTodayDate(); // Get today's date in YYYY-MM-DD format // setDateFrom(today); // setDateTo(today); // }; const handleDelete = async (id) => { const result = await confirmHoldToDelete({ title: "Are you sure?", text: "You won't be able to revert this!", }); if (result.isConfirmed) { try { const response = await fetch(`${BASE_URL}/api/attendance/delete_attendance?id=${id}`, { method: "DELETE", }); const data = await response.json(); if (data.success) { Swal.fire({ icon: "success", title: "Deleted!", text: "Record deleted successfully.", }); fetchAttendance(); } else { Swal.fire({ icon: "error", title: "Oops!", text: data.message || "Failed to delete the record.", }); } } catch (error) { Swal.fire({ icon: "error", title: "Error!", text: "Error deleting record: " + error.message, }); } } }); }; const openModal = (data = null) => { setModalData(data); setModalVisible(true); }; const closeModal = () => { setModalData(null); setModalVisible(false); fetchAttendance(); // Refresh after create/edit }; // Utility function to format time from military to 12-hour format const formatTime = (time) => { if (!time) return "N/A"; const [hours, minutes] = time.split(':'); const suffix = hours >= 12 ? 'PM' : 'AM'; const formattedHours = hours % 12 || 12; // Convert 0 to 12 return `${formattedHours}:${minutes} ${suffix}`; } // Initialize state with the current date const [currentDate, setCurrentDate] = useState(new Date()); // Function to go to the previous month const handlePreviousMonth = () => { const newDate = new Date(currentDate); newDate.setMonth(newDate.getMonth() - 1); setCurrentDate(newDate); }; // Function to go to the next month const handleNextMonth = () => { const newDate = new Date(currentDate); newDate.setMonth(newDate.getMonth() + 1); setCurrentDate(newDate); }; // Format the date for display const formattedDate = currentDate.toLocaleString('default', { weekday: 'long', day: 'numeric', month: 'long', }); const formatDateTime = (dateString) => { const options = { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true // Use 12-hour format }; const date = new Date(dateString); return date.toLocaleString('en-US', options); }; // Get the weekdays for the current month const showToday = () => { const today = new Date().toISOString().split('T')[0]; // Get today's date in YYYY-MM-DD format setDateFrom(today); setDateTo(today); }; const calculateAttendanceSummary = () => { let onTimeMorningCount = 0; let earlyMorningCount = 0; let lateMorningCount = 0; let onTimeAfternoonCount = 0; let earlyAfternoonCount = 0; let lateAfternoonCount = 0; filteredAttendanceData.forEach(item => { // Morning calculations const timeInMorning = item.time_in_morning; // Assuming this is in "HH:mm" format if (timeInMorning) { const [hours, minutes] = timeInMorning.split(':').map(Number); const timeInMorningDate = new Date(); timeInMorningDate.setHours(hours, minutes); const morningOnTimeThreshold = new Date(); morningOnTimeThreshold.setHours(9, 0); // 9:00 AM if (timeInMorningDate.getTime() === morningOnTimeThreshold.getTime()) { onTimeMorningCount++; } else if (timeInMorningDate < morningOnTimeThreshold) { earlyMorningCount++; } else if (timeInMorningDate > morningOnTimeThreshold) { lateMorningCount++; } } // Afternoon calculations const timeInAfternoon = item.time_in_afternoon; // Assuming this is in "HH:mm" format if (timeInAfternoon) { const [hours, minutes] = timeInAfternoon.split(':').map(Number); const timeInAfternoonDate = new Date(); timeInAfternoonDate.setHours(hours, minutes); const afternoonOnTimeThreshold = new Date(); afternoonOnTimeThreshold.setHours(13, 0); // 1:00 PM if (timeInAfternoonDate.getTime() === afternoonOnTimeThreshold.getTime()) { onTimeAfternoonCount++; } else if (timeInAfternoonDate < afternoonOnTimeThreshold) { earlyAfternoonCount++; } else if (timeInAfternoonDate > afternoonOnTimeThreshold) { lateAfternoonCount++; } } }); return { onTimeMorningCount, earlyMorningCount, lateMorningCount, onTimeAfternoonCount, earlyAfternoonCount, lateAfternoonCount, }; }; // Filter attendance data based on search query const filteredAttendanceData = attendanceData.filter(item => { const itemDate = new Date(item.attendance_date).toISOString().split('T')[0]; return ( (!dateFrom || itemDate >= dateFrom) && // Check if itemDate is greater than or equal to dateFrom (!dateTo || itemDate <= dateTo) && // Check if itemDate is less than or equal to dateTo (() => { const safeQuery = searchQuery.replace(/\s+/g, " ").toLowerCase().trim(); const queryWords = safeQuery ? safeQuery.split(" ") : []; const haystack = [ item.employee_name, item.employee_id, item.attendance_id, item.biometrics_id, item.person_id ] .filter(Boolean) .join(" ") .toLowerCase() .replace(/\s+/g, " "); return queryWords.length === 0 || queryWords.every(word => haystack.includes(word)); })() ); }); const sortedAttendanceData = filteredAttendanceData.sort((a, b) => { return new Date(b.attendance_date) - new Date(a.attendance_date); }); // const { onTimeCount, earlyCount, lateCount } = calculateAttendanceSummary(); const { onTimeMorningCount, earlyMorningCount, lateMorningCount, onTimeAfternoonCount, earlyAfternoonCount, lateAfternoonCount, } = calculateAttendanceSummary(); const breadcrumbItems = [ !permLoading && permissions?.attendance_dtr && { label: 'Horizon Time & Attendance', path: '/attendanceRecord' }, !permLoading && permissions?.attendance_log && { label: 'Attendance Logs', path: '/attendance' }, !permLoading && permissions?.leave_access && { label: 'Manage Leave', path: '/ApproveLeavePage' }, !permLoading && permissions?.schedule_management && { label: 'Schedule Management', path: '/ShiftSchedulePage' }, ].filter(Boolean); // remove any falsy (unauthorized) entries const SummaryItem = ({ label, value }) => (

{label}

{value}

); return (
{!isAttendanceSummary && (
Attendance Logs
)} <>
{/* PRESENT SUMMARY CARD */}
{/* Header */} {/* Content */}

Morning Summary

Afternoon Summary

{/* AWAY SUMMARY CARD */}
{modalVisible && ( )}
{/* FILTER INPUTS + BUTTONS */}
{/* Search Input */}
setSearchQuery(e.target.value)} className="w-full px-4 py-2 text-sm transition border border-gray-300 rounded-full outline-none focus:ring-2 focus:ring-blue-400" />
{/* Date Range Inputs */} {user?.role === "ADMIN" && (
setDateFrom(e.target.value)} className="w-full px-4 py-2 text-sm border border-gray-300 rounded-full outline-none sm:w-40 focus:ring-2 focus:ring-blue-400" />
setDateTo(e.target.value)} className="w-full px-4 py-2 text-sm border border-gray-300 rounded-full outline-none sm:w-40 focus:ring-2 focus:ring-blue-400" />
)} {/* Show Today Button */}
{/* Responsive Attendance List */} {loading ? (

Loading...

) : filteredAttendanceData.length > 0 ? (
{sortedAttendanceData.map((item) => (
{/* Top Section */}

{item.employee_name}

Emp. ID: {item.employee_id} • Atten. ID:{" "} {item.attendance_id}
{new Date(item.attendance_date).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric", })}
{/* Attendance Times */}

Morning

{isMidnight(formatTime(item.time_in_morning)) ? "N/A" : formatTime(item.time_in_morning)}{" "} →{" "} {isMidnight(formatTime(item.time_out_morning)) ? "N/A" : formatTime(item.time_out_morning)}

{item.work_location && (

Loc: {item.work_location}

)}

Afternoon

{isMidnight(formatTime(item.time_in_afternoon)) ? "N/A" : formatTime(item.time_in_afternoon)}{" "} →{" "} {isMidnight(formatTime(item.time_out_afternoon)) ? "N/A" : formatTime(item.time_out_afternoon)}

{/* Summary Info */}

Credited

{item.days_credited}

Deducted

{item.deducted_days}

Overtime

{item.overtime_hours}

Created At

{formatDateTime(item.create_at)}

{/* For Mobile Created At */}
Created: {formatDateTime(item.create_at)}
))}
) : (

No attendance records.

)}
); }; export default Attendance;