import { useEffect, useState, useRef } from "react"; import BASE_URL from "@backend/server/config"; import axiosInstance from "../../utils/axiosInstance"; import Swal from "sweetalert2"; import { Card, CardContent, Typography, Checkbox, Divider, FormControl, InputLabel, Select, MenuItem, Button, } from "@mui/material"; import { Trash2, Search, LayoutGrid, Clock3, CheckCircle2, XCircle, } from "lucide-react"; import StatusSummary from "../../admin_Late_request/components/commons/statusSummaryLateAtt"; import AttendanceRequestActions from "../../admin_Late_request/components/commons/requestActionButton"; import { useSession } from "../../../context/SessionContext"; import usePermissions from "../../../users/hooks/usePermissions"; import { usePendingCounts } from "../../../context/PendingCountsContext"; import { exportRequestsXLSX } from "../../../utils/exportRequestsXLSX"; const OvertimeRequest = () => { const { user } = useSession(); const { permissions, loading: permLoading } = usePermissions(user?.username); const [overtimeRequests, setOvertimeRequests] = useState([]); const [loading, setLoading] = useState(true); const [statusFilter, setStatusFilter] = useState("Pending"); const [sortAscending, setSortAscending] = useState(true); const [selectedRequests, setSelectedRequests] = useState(new Set()); const [searchQuery, setSearchQuery] = useState(""); const [selectedBulkStatus, setSelectedBulkStatus] = useState("pending"); const [isApplyingBulk, setIsApplyingBulk] = useState(false); const [viewArchived, setViewArchived] = useState(false); const [currentPage, setCurrentPage] = useState(1); const itemsPerPage = 12; const REQUESTS_UPDATED_EVENT = "requests:updated"; const { counts } = usePendingCounts(); const prevOvertimePending = useRef(counts?.overtimePending); useEffect(() => { setCurrentPage(1); }, [viewArchived, searchQuery, statusFilter]); useEffect(() => { if (counts && prevOvertimePending.current !== undefined) { if (counts.overtimePending !== prevOvertimePending.current) { fetchOvertimeRequests(); } } prevOvertimePending.current = counts?.overtimePending; }, [counts?.overtimePending]); const fetchOvertimeRequests = async () => { setLoading(true); try { const endpoint = viewArchived ? "get_archived_overtime" : "overtime_request"; const response = await axiosInstance.get(`${BASE_URL}/api/overtime/${endpoint}`); const data = response.data; if (data.success) { setOvertimeRequests(data.data); } else { console.error("Failed to fetch overtime requests"); setOvertimeRequests([]); } } catch (error) { console.error("Error fetching overtime requests:", error); setOvertimeRequests([]); } finally { setLoading(false); } }; useEffect(() => { fetchOvertimeRequests(); }, [viewArchived]); const handleExportArchive = async () => { try { const response = await axiosInstance.get( `${BASE_URL}/api/overtime/export_overtime_archive`, { responseType: "text" } ); await exportRequestsXLSX(response.data, "archived_overtime_requests.xlsx", "Archived Overtime Requests"); } catch (err) { console.error(err); Swal.fire({ icon: "error", title: "Export Failed" }); } }; const handleExportActive = async () => { try { const response = await axiosInstance.get( `${BASE_URL}/api/overtime/export_active_overtime`, { responseType: "text" } ); await exportRequestsXLSX(response.data, "active_overtime_requests.xlsx", "Active Overtime Requests"); } catch (err) { console.error(err); Swal.fire({ icon: "error", title: "Export Failed" }); } }; const updateStatus = async ( id, newStatus, { silent = false, skipRefresh = false } = {}, ) => { try { const formData = new URLSearchParams({ id, status: newStatus, approved_by: newStatus === "Approved" ? "Admin" : "Not approved yet", }); const response = await axiosInstance.post( `${BASE_URL}/api/overtime/edit_overtime_request`, formData.toString(), { headers: { "Content-Type": "application/x-www-form-urlencoded" } }, ); const data = response.data; if (data.success) { if (!silent) { Swal.fire({ icon: "success", title: "Status updated successfully!", showConfirmButton: false, timer: 1500, }); } if (!skipRefresh) { fetchOvertimeRequests(); } window.dispatchEvent(new CustomEvent(REQUESTS_UPDATED_EVENT)); } else { if (!silent) { Swal.fire({ icon: "error", title: "Failed to update status", text: data.message, }); } return { success: false, message: data.message }; } return { success: true }; } catch (error) { console.error("Error updating status:", error); if (!silent) { Swal.fire({ icon: "error", title: "Error updating status", text: "An unexpected error occurred!", }); } return { success: false, message: error?.message || "Unexpected error" }; } }; const applyBulkStatus = async () => { if (selectedRequests.size === 0) { Swal.fire({ icon: "warning", title: "No selection", text: "Select requests first", timer: 1500, showConfirmButton: false, }); return; } setIsApplyingBulk(true); try { const ids = Array.from(selectedRequests); // Single bulk request instead of N individual requests const response = await axiosInstance.post( `${BASE_URL}/api/overtime/bulk_update_overtime_status`, { request_ids: ids, status: selectedBulkStatus }, ); const data = response.data; if (data.failed > 0) { Swal.fire("Partial update", `${data.updated} updated, ${data.failed} failed.`, "warning"); } else { Swal.fire({ icon: "success", title: "Updated", showConfirmButton: false, timer: 1500 }); } setSelectedRequests(new Set()); await fetchOvertimeRequests(); window.dispatchEvent(new CustomEvent(REQUESTS_UPDATED_EVENT)); } catch (error) { Swal.fire({ icon: "error", title: "Error", text: error?.message || "Failed to update selected requests.", }); } finally { setIsApplyingBulk(false); } }; const filteredAndSortedRequests = () => { const normalizedQuery = searchQuery.trim().toLowerCase(); const matchesSearch = (req) => { if (!normalizedQuery) return true; const requestDate = new Date(req.date_requested); const isoDate = isNaN(requestDate) ? "" : requestDate.toISOString().split("T")[0]; const displayDate = isNaN(requestDate) ? "" : requestDate.toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric", }); const safeQuery = normalizedQuery.replace(/\s+/g, " ").trim(); const queryWords = safeQuery ? safeQuery.split(" ") : []; const haystack = [ req.employee_name, req.employee_id, req.request_id, req.attendance_id, req.date_requested, isoDate, displayDate, ] .filter(Boolean) .join(" ") .toLowerCase() .replace(/\s+/g, " "); return queryWords.length === 0 || queryWords.every(word => haystack.includes(word)); }; return overtimeRequests .filter((req) => statusFilter === "All" ? true : req.status === statusFilter, ) .filter(matchesSearch) .sort((a, b) => sortAscending ? new Date(a.date_requested) - new Date(b.date_requested) : new Date(b.date_requested) - new Date(a.date_requested), ); }; const visibleRequests = filteredAndSortedRequests(); const paginatedRequests = viewArchived ? visibleRequests.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage) : visibleRequests; const handleSelectAll = (e) => { if (e.target.checked) { setSelectedRequests(new Set(paginatedRequests.map((r) => r.request_id))); } else { setSelectedRequests(new Set()); } }; const handleSelectRequest = (id) => { const updated = new Set(selectedRequests); updated.has(id) ? updated.delete(id) : updated.add(id); setSelectedRequests(updated); }; const handleDeleteSelected = async () => { const confirmed = await Swal.fire({ title: "Are you sure?", text: "This will delete all selected requests!", icon: "warning", showCancelButton: true, confirmButtonText: "Yes, delete them!", }); if (confirmed.isConfirmed) { try { const ids = Array.from(selectedRequests); // Single bulk request instead of N individual DELETE requests const response = await axiosInstance.post( `${BASE_URL}/api/overtime/bulk_delete_overtime_requests`, { request_ids: ids }, ); const data = response.data; if (data.success) { Swal.fire({ icon: "success", title: `${data.deleted} request(s) deleted!`, showConfirmButton: false, timer: 1500, }); } else { Swal.fire({ icon: "error", title: "Error", text: "Failed to delete requests: " + data.message }); } } catch (error) { console.error(error); Swal.fire({ icon: "error", title: "Error deleting requests" }); } finally { setSelectedRequests(new Set()); fetchOvertimeRequests(); window.dispatchEvent(new CustomEvent(REQUESTS_UPDATED_EVENT)); } } }; const countRequestsByStatus = () => { const counts = { Pending: 0, Approved: 0, Rejected: 0 }; overtimeRequests.forEach((r) => { if (r.status in counts) counts[r.status]++; }); return counts; }; const requestCounts = countRequestsByStatus(); if (loading) return
Loading...
; return (
a + b, 0) }} activeStatus={statusFilter} onSelectStatus={setStatusFilter} isArchived={viewArchived} /> 0 && selectedRequests.size === paginatedRequests.length } isPartiallySelected={ selectedRequests.size > 0 && selectedRequests.size < paginatedRequests.length } onDeleteSelected={handleDeleteSelected} isDeleteDisabled={selectedRequests.size === 0} sortAscending={sortAscending} onToggleSort={setSortAscending} selectedBulkStatus={selectedBulkStatus} onBulkStatusChange={setSelectedBulkStatus} onApplyBulkStatus={applyBulkStatus} isApplyDisabled={selectedRequests.size === 0 || isApplyingBulk} searchQuery={searchQuery} onSearchQueryChange={setSearchQuery} isArchived={viewArchived} />
{visibleRequests.length === 0 ? (
No overtime requests found for{" "} {statusFilter} {searchQuery.trim() ? ( <> {" "} matching{" "} "{searchQuery.trim()}" ) : null} .
) : ( paginatedRequests.map((r) => (
{!viewArchived && ( handleSelectRequest(r.request_id)} size="small" /> )} {r.employee_name}
ID: {r.employee_id} Reason: {r.reason || "No reason provided"} Request Date:{" "} {new Date(r.date_requested).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric", })} Time start:{" "} {new Date( `1970-01-01T${r.time_start}`, ).toLocaleTimeString([], { hour: "numeric", minute: "2-digit", })}{" "} | Time end:{" "} {new Date(`1970-01-01T${r.end_time}`).toLocaleTimeString( [], { hour: "numeric", minute: "2-digit" }, )} OT Credit Days:{" "} {r.overtime_credit ?? r.hours_requested} |{" "} {r.minutes_requested ?? 0} min OT Amount:{" "} PHP{" "} {Number(r.overtime_amount ?? 0).toLocaleString("en-PH", { minimumFractionDigits: 2, maximumFractionDigits: 2, })} {/* Status control moved to top toolbar (bulk apply). */}
)) )}
{viewArchived && Math.ceil(visibleRequests.length / itemsPerPage) > 1 && (
Page {currentPage} of {Math.ceil(visibleRequests.length / itemsPerPage)}
)}
); }; export default OvertimeRequest;