import React, { useState, useEffect, useRef } from "react"; import { useNavigate, useLocation } from "react-router-dom"; import { useAuth } from "@/lib/auth-context"; import { format, getYear, parseISO, differenceInCalendarDays } from "date-fns"; import { CalendarPlus, Loader2, Calendar as CalendarIcon, Filter, SearchIcon, X, CheckCircle, XCircle, AlertCircle, FileText, Clock, Download, User, BarChart3, TrendingUp, ShieldAlert, Briefcase, Home, Info, RefreshCw, Gift, CalendarDays, ChevronDown } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { Badge } from "@/components/ui/badge"; import { Separator } from "@/components/ui/separator"; import { toast } from "sonner"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { Leave, LeaveBalance as LeaveBalanceType, LeaveCreateRequest, LeaveStatus, LeaveType, Holiday } from "@/types"; import { leaveApi } from "@/services/leaveApi"; import { employeeApi } from "@/services/employeeApi"; import LeaveForm from "@/components/leaves/LeaveForm"; import LeaveCard from "@/components/leaves/LeaveCard"; import CustomLeaveDetailDialog from "@/components/leaves/CustomLeaveDetailDialog"; import LeaveBalanceComponent from "@/components/leaves/LeaveBalance"; import Header from "@/components/layout/header"; import Sidebar from "@/components/layout/sidebar"; import { useIsMobile } from "@/hooks/use-mobile"; import { Label } from "@/components/ui/label"; import CustomDialog from "@/components/CustomDialog"; import { Progress } from "@/components/ui/progress"; import { cn } from "@/lib/utils"; import { holidayApi } from "@/services/holidayApi"; import { leaveMasterApi } from "@/services/leaveMasterApi"; import { LeaveMaster } from "@/types"; // Custom Status Filter component function CustomStatusFilter({ value, onValueChange }) { const [open, setOpen] = useState(false); const dropdownRef = useRef(null); const statusOptions = [ { value: "All", label: "All Statuses" }, { value: "Pending", label: "Pending" }, { value: "Approved", label: "Approved" }, { value: "Rejected", label: "Rejected" }, { value: "Cancelled", label: "Cancelled" } ]; useEffect(() => { if (!open) return; const handleOutsideClick = (e: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) { setOpen(false); } }; document.addEventListener('mousedown', handleOutsideClick); return () => document.removeEventListener('mousedown', handleOutsideClick); }, [open]); const getStatusColor = (status: string) => { switch (status) { case "Pending": return "text-amber-500"; case "Approved": return "text-green-500"; case "Rejected": return "text-red-500"; case "Cancelled": return "text-gray-500"; default: return ""; } }; const selectedOption = statusOptions.find(option => option.value === value) || statusOptions[0]; return (
{open && (
{statusOptions.map((option) => ( ))}
)}
); } // Custom Type Filter component function CustomTypeFilter({ value, onValueChange }) { const [open, setOpen] = useState(false); const dropdownRef = useRef(null); const typeOptions = [ { value: "All", label: "All Types" }, { value: "Annual", label: "Annual" }, { value: "Unpaid", label: "Unpaid" }, { value: "Compensatory", label: "Compensatory" }, { value: "Half Day", label: "Half Day" }, { value: "Other", label: "Other" } ]; useEffect(() => { if (!open) return; const handleOutsideClick = (e: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) { setOpen(false); } }; document.addEventListener('mousedown', handleOutsideClick); return () => document.removeEventListener('mousedown', handleOutsideClick); }, [open]); const getTypeColor = (type: string) => { switch (type) { case "Annual": return "text-blue-500"; case "Sick": return "text-red-500"; case "Personal": return "text-green-500"; case "Unpaid": return "text-gray-500"; case "Maternity": return "text-pink-500"; case "Paternity": return "text-purple-500"; case "Bereavement": return "text-slate-500"; case "Compensatory": return "text-orange-500"; case "Half Day": return "text-amber-500"; case "Other": return "text-neutral-500"; default: return ""; } }; const selectedOption = typeOptions.find(option => option.value === value) || typeOptions[0]; return (
{open && (
{typeOptions.map((option) => ( ))}
)}
); } // Custom Year Filter component function CustomYearFilter({ value, onValueChange, years }) { const [open, setOpen] = useState(false); const dropdownRef = useRef(null); useEffect(() => { if (!open) return; const handleOutsideClick = (e: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) { setOpen(false); } }; document.addEventListener('mousedown', handleOutsideClick); return () => document.removeEventListener('mousedown', handleOutsideClick); }, [open]); return (
{open && (
{years.map((year) => ( ))}
)}
); } const LeavesPage: React.FC = () => { const navigate = useNavigate(); const location = useLocation(); const { userData } = useAuth(); const isMobile = useIsMobile(); // Log userData for debugging console.log('LeavesPage - userData:', userData); const [isSidebarOpen, setIsSidebarOpen] = useState(!isMobile); // Close sidebar on mobile when route changes useEffect(() => { if (isMobile) { setIsSidebarOpen(false); } }, [location, isMobile]); // Update sidebar state when screen size changes useEffect(() => { setIsSidebarOpen(!isMobile); }, [isMobile]); const [isLoading, setIsLoading] = useState(true); const [leaves, setLeaves] = useState([]); const [filteredLeaves, setFilteredLeaves] = useState([]); const [selectedLeave, setSelectedLeave] = useState(null); const [leaveBalance, setLeaveBalance] = useState([]); const [isBalanceLoading, setIsBalanceLoading] = useState(true); const [apiError, setApiError] = useState(null); const [leaveMasters, setLeaveMasters] = useState([]); const [isLeaveMastersLoading, setIsLeaveMastersLoading] = useState(false); const [duplicateDateWarning, setDuplicateDateWarning] = useState(""); // Dialog state const [showNewLeaveDialog, setShowNewLeaveDialog] = useState(false); const [isCompOffRequest, setIsCompOffRequest] = useState(false); const [isWFHRequest, setIsWFHRequest] = useState(false); const [showLeaveDetailDialog, setShowLeaveDetailDialog] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); // Filter states const [searchTerm, setSearchTerm] = useState(""); const [statusFilter, setStatusFilter] = useState("All"); const [typeFilter, setTypeFilter] = useState("All"); const [yearFilter, setYearFilter] = useState(getYear(new Date())); // Get array of years for filtering (current year and 2 years back) const currentYear = getYear(new Date()); const years = [currentYear, currentYear - 1, currentYear - 2]; const [activeTab, setActiveTab] = useState("all"); // Helper function to clear query parameters const clearQueryParams = () => { navigate('/leaves', { replace: true }); }; // Handle query parameters to auto-open dialogs useEffect(() => { const searchParams = new URLSearchParams(location.search); const wfh = searchParams.get('wfh'); const compoff = searchParams.get('compoff'); if (wfh === 'true' && !showNewLeaveDialog) { setIsWFHRequest(true); setIsCompOffRequest(false); setShowNewLeaveDialog(true); } else if (compoff === 'true' && !showNewLeaveDialog) { setIsCompOffRequest(true); setIsWFHRequest(false); setShowNewLeaveDialog(true); } }, [location.search, showNewLeaveDialog]); useEffect(() => { const fetchLeaves = async () => { if (!userData?.employeeId) { console.log("No employee ID found in user data, skipping API call"); setIsLoading(false); return; } setIsLoading(true); setApiError(null); try { // Using employee ID 26 for testing as per the curl example const employeeId = userData?.employeeId; // Use hardcoded ID for testing console.log(`Fetching leaves for employee ID: ${employeeId} (testing with fixed ID)`); const leaves = await leaveApi.getByEmployeeId(employeeId); console.log(`Received ${leaves.length} leave records`); setLeaves(leaves); setFilteredLeaves(leaves); } catch (error) { console.error("Error fetching leaves:", error); setApiError("Failed to connect to leaves API. Please check network connection and API availability."); toast.error("Failed to fetch leave records"); } finally { setIsLoading(false); } }; const fetchLeaveBalance = async () => { setIsBalanceLoading(true); try { if (userData?.employeeId) { const data = await leaveApi.getLeaveBalance(userData.employeeId, currentYear); if (Array.isArray(data)) { setLeaveBalance(data); console.log('Leave balance data:', data); } else { console.error('Invalid leave balance data format:', data); setLeaveBalance([]); } } } catch (error) { console.error('Error fetching leave balance:', error); setLeaveBalance([]); } finally { setIsBalanceLoading(false); } }; const fetchLeaveMasters = async () => { setIsLeaveMastersLoading(true); try { const masters = await leaveMasterApi.getAll(); setLeaveMasters(masters); console.log('Leave masters data:', masters); } catch (error) { console.error('Error fetching leave masters:', error); setLeaveMasters([]); } finally { setIsLeaveMastersLoading(false); } }; fetchLeaves(); fetchLeaveBalance(); fetchLeaveMasters(); }, [userData?.employeeId, currentYear]); // Apply filters whenever filter states change useEffect(() => { if (!leaves.length) return; let filtered = [...leaves]; // Filter by year filtered = filtered.filter(leave => { const leaveYear = getYear(new Date(leave.startDate)); return leaveYear === yearFilter; }); // Filter by status based on active tab if (activeTab === "pending") { filtered = filtered.filter(leave => leave.status === "Pending"); } else if (activeTab === "approved") { filtered = filtered.filter(leave => leave.status === "Approved"); } else if (activeTab === "rejected") { filtered = filtered.filter(leave => leave.status === "Rejected"); } else if (statusFilter !== "All") { // Only apply statusFilter if not in a specific tab filtered = filtered.filter(leave => leave.status === statusFilter); } // Filter by type if (typeFilter !== "All") { filtered = filtered.filter(leave => leave.type === typeFilter); } // Filter by search term (reason) if (searchTerm) { filtered = filtered.filter(leave => leave.reason.toLowerCase().includes(searchTerm.toLowerCase()) ); } setFilteredLeaves(filtered); }, [leaves, searchTerm, statusFilter, typeFilter, yearFilter, activeTab]); // Toggle sidebar const toggleSidebar = () => { setIsSidebarOpen(!isSidebarOpen); }; const handleSubmitLeave = async (data: LeaveCreateRequest) => { if (!userData?.employeeId) return; // Check for overlapping leave request date ranges const newStartDate = new Date(data.startDate); const newEndDate = new Date(data.endDate); const overlappingLeave = leaves.find(leave => { const existingStartDate = new Date(leave.startDate); const existingEndDate = new Date(leave.endDate); // Check if date ranges overlap // Overlap occurs if: new start <= existing end AND new end >= existing start return newStartDate <= existingEndDate && newEndDate >= existingStartDate; }); if (overlappingLeave && overlappingLeave.status !== "Cancelled") { const warningMsg = `This leave request overlaps with an existing leave from ${format(new Date(overlappingLeave.startDate), 'PPP')} to ${format(new Date(overlappingLeave.endDate), 'PPP')} (Status: ${overlappingLeave.status})`; setDuplicateDateWarning(warningMsg); toast.error("Leave request overlaps with an existing leave"); return; } setDuplicateDateWarning(""); setIsSubmitting(true); try { // Set the employee ID from the authenticated user and required fields data.id = 0; // Required by API data.employeeId = userData.employeeId; data.status = "Pending"; data.appliedOn = new Date().toISOString(); console.log("Submitting leave request with data:", data); // Submit the leave request const newLeave = await leaveApi.create(data); // Upload document if needed if (newLeave && data.documents) { // This part would need to be implemented if you have document upload functionality } // Refresh leaves list const updatedLeaves = await leaveApi.getByEmployeeId(userData.employeeId); setLeaves(updatedLeaves); // Close dialog setShowNewLeaveDialog(false); setIsCompOffRequest(false); setIsWFHRequest(false); clearQueryParams(); toast.success("Leave request submitted successfully"); } catch (error) { console.error("Error submitting leave request:", error); toast.error("Failed to submit leave request"); } finally { setIsSubmitting(false); } }; const handleCancelLeave = async (leave: Leave) => { if (!userData?.employeeId || !leave) return; if (window.confirm("Are you sure you want to cancel this leave request?")) { try { const updateData = { id: leave.id, status: "Cancelled" as LeaveStatus, approvedById: userData.employeeId, actionDate: format(new Date(), "yyyy-MM-dd'T'HH:mm:ss"), comments: "Cancelled by employee", }; await leaveApi.updateStatus(updateData); // Refresh leaves list const updatedLeaves = await leaveApi.getByEmployeeId(userData.employeeId); setLeaves(updatedLeaves); toast.success("Leave request cancelled successfully"); } catch (error) { console.error("Error cancelling leave request:", error); toast.error("Failed to cancel leave request"); } } }; const handleViewLeaveDetails = (leave: Leave) => { setSelectedLeave(leave); setShowLeaveDetailDialog(true); }; const resetFilters = () => { setSearchTerm(""); setStatusFilter("All"); setTypeFilter("All"); setYearFilter(currentYear); }; return (
{/* Header with title and action buttons */}

My Leave Management

Request and track your time off from work

{/* */}
{ if (!isSubmitting) { setShowNewLeaveDialog(false); setIsCompOffRequest(false); setIsWFHRequest(false); clearQueryParams(); } }} title={isWFHRequest ? "New Work From Home Request" : isCompOffRequest ? "New Comp Off Request" : "New Leave Request"} description={isWFHRequest ? "Submit a new work from home request for approval" : isCompOffRequest ? "Submit a new compensatory off request for approval" : "Submit a new leave request for approval"} allowClose={!isSubmitting} isPending={isSubmitting} > {userData && ( { setShowNewLeaveDialog(false); setIsCompOffRequest(false); setIsWFHRequest(false); setDuplicateDateWarning(""); clearQueryParams(); }} isSubmitting={isSubmitting} defaultType={isWFHRequest ? "Work From Home" : isCompOffRequest ? "Compensatory" : ""} defaultCompOff={isCompOffRequest} hideLeaveType={isWFHRequest} duplicateDateWarning={duplicateDateWarning} onDateChange={() => setDuplicateDateWarning("")} /> )}
{/* Left column for leave requests */}
{/* Filters */}
Filter Leaves {(searchTerm || statusFilter !== "All" || typeFilter !== "All" || yearFilter !== currentYear) && ( )}
setSearchTerm(e.target.value)} className="pl-8 bg-white dark:bg-gray-950" />
setStatusFilter(val as LeaveStatus | "All")} /> setTypeFilter(val as LeaveType | "All")} /> setYearFilter(parseInt(val))} years={years} />
{/* Tabs and leave list */} All Pending Approved Rejected {renderLeavesListEnhanced()} {renderLeavesListEnhanced()} {renderLeavesListEnhanced()} {renderLeavesListEnhanced()}
{/* Right column for leave balance and policies */}
{/* Enhanced Leave Balance Component */} Leave Balance ({currentYear}) {renderEnhancedLeaveBalance()} {/* Leave Policies Card */} Leave Policies
  • Leave requests must be submitted at least 3 days in advance
  • Sick leave requires a medical certificate for 3+ days
  • Annual leave is accrued based on years of service
  • Unused leave may be carried forward based on company policy
  • Contact HR for special leave circumstances
{/* Additional Card: Quick Links */} Quick Links
{/* Upcoming Holidays Card */} Upcoming Holidays
{/* Leave Detail Dialog */} {selectedLeave && ( setShowLeaveDetailDialog(false)} onDownloadDocument={leave => { if (leave.documents) { window.open(leave.documents, '_blank'); } }} /> )}
); function renderEnhancedLeaveBalance() { if (isBalanceLoading || isLeaveMastersLoading) { return (
); } if (!leaveBalance || leaveBalance.length === 0) { return (

No leave balance information available

); } // Build dynamic leave type map from fetched leave masters const leaveTypeMap: Record = {}; leaveMasters.forEach(master => { // Map leave types to colors (you can expand this mapping as needed) const getTypeColors = (type: string) => { switch (type.toLowerCase()) { case "annual": return { color: "bg-blue-500", bgColor: "bg-blue-100", lightColor: "text-blue-600" }; case "sick": return { color: "bg-red-500", bgColor: "bg-red-100", lightColor: "text-red-600" }; case "personal": return { color: "bg-green-500", bgColor: "bg-green-100", lightColor: "text-green-600" }; case "maternity": return { color: "bg-pink-500", bgColor: "bg-pink-100", lightColor: "text-pink-600" }; case "paternity": return { color: "bg-purple-500", bgColor: "bg-purple-100", lightColor: "text-purple-600" }; case "bereavement": return { color: "bg-slate-500", bgColor: "bg-slate-100", lightColor: "text-slate-600" }; case "compensatory": return { color: "bg-orange-500", bgColor: "bg-orange-100", lightColor: "text-orange-600" }; case "half day": return { color: "bg-amber-500", bgColor: "bg-amber-100", lightColor: "text-amber-600" }; case "casual": return { color: "bg-emerald-500", bgColor: "bg-emerald-100", lightColor: "text-emerald-600" }; case "unpaid": return { color: "bg-gray-500", bgColor: "bg-gray-100", lightColor: "text-gray-600" }; case "work from home": return { color: "bg-teal-500", bgColor: "bg-teal-100", lightColor: "text-teal-600" }; default: return { color: "bg-neutral-500", bgColor: "bg-neutral-100", lightColor: "text-neutral-600" }; } }; const colors = getTypeColors(master.leaveType); leaveTypeMap[master.id] = { label: master.leaveType, ...colors }; }); // Function to calculate percentage used const calculatePercentage = (used: number, total: number) => { if (total === 0) return 0; return Math.min((used / total) * 100, 100); // Cap at 100% }; return (
{leaveBalance.map((balance) => { const typeInfo = leaveTypeMap[balance.leavesMasterId] || { label: `Leave Type ${balance.leavesMasterId}`, color: "bg-gray-500", bgColor: "bg-gray-100", lightColor: "text-gray-600" }; const percentage = calculatePercentage(balance.used, balance.allocated); const remaining = balance.pending; // Check if this is Annual or Compensatory leave type const isAnnualOrCompensatory = typeInfo.label.toLowerCase() === "annual" || typeInfo.label.toLowerCase() === "compensatory"; return (
{typeInfo.label}
{isAnnualOrCompensatory ? ( {remaining} / {balance.allocated} ) : ( {balance.used} used )}
{isAnnualOrCompensatory && (
)}
{isAnnualOrCompensatory ? ( <> Used: {balance.used} days {balance.carryForward > 0 && ( Includes {balance.carryForward} carried forward )} ) : ( Used: {balance.used} days )}
); })}
); } function renderLeavesListEnhanced() { if (isLoading) { return (
); } if (apiError) { return (

API Connection Error

{apiError}

); } if (filteredLeaves.length === 0) { return (

No leaves found

No leave records found for the selected filters

); } return (
{filteredLeaves.map((leave) => ( { setSelectedLeave(leave); setShowLeaveDetailDialog(true); }} onCancel={handleCancelLeave} /> ))}
); } // A more colorful version of the leave card function EnhancedLeaveCard({ leave, onViewDetails, onCancel }: { leave: Leave, onViewDetails: () => void, onCancel?: (leave: Leave) => void }) { const startDate = new Date(leave.startDate); const endDate = new Date(leave.endDate); const durationDays = leave.workingDays || (differenceInCalendarDays(endDate, startDate) + 1); // Debug logging for updatedByName if (leave.status === "Approved" || leave.status === "Rejected") { console.log('Leave ID:', leave.id, 'Status:', leave.status, 'actionDate:', leave.actionDate, 'updatedByName:', leave.updatedByName, 'Full leave object:', leave); } // Get background and text colors based on leave type const getTypeColors = (type: LeaveType) => { switch (type) { case "Annual": return { bg: "bg-blue-50 dark:bg-blue-900/20", border: "border-blue-200 dark:border-blue-800", text: "text-blue-700 dark:text-blue-300" }; case "Sick": return { bg: "bg-red-50 dark:bg-red-900/20", border: "border-red-200 dark:border-red-800", text: "text-red-700 dark:text-red-300" }; case "Personal": return { bg: "bg-green-50 dark:bg-green-900/20", border: "border-green-200 dark:border-green-800", text: "text-green-700 dark:text-green-300" }; case "Unpaid": return { bg: "bg-orange-50 dark:bg-orange-900/20", border: "border-orange-200 dark:border-orange-800", text: "text-orange-700 dark:text-orange-300" }; case "Maternity": return { bg: "bg-pink-50 dark:bg-pink-900/20", border: "border-pink-200 dark:border-pink-800", text: "text-pink-700 dark:text-pink-300" }; case "Paternity": return { bg: "bg-purple-50 dark:bg-purple-900/20", border: "border-purple-200 dark:border-purple-800", text: "text-purple-700 dark:text-purple-300" }; case "Bereavement": return { bg: "bg-slate-50 dark:bg-slate-900/20", border: "border-slate-200 dark:border-slate-800", text: "text-slate-700 dark:text-slate-300" }; case "Compensatory": return { bg: "bg-amber-50 dark:bg-amber-900/20", border: "border-amber-200 dark:border-amber-800", text: "text-amber-700 dark:text-amber-300" }; case "Half Day": return { bg: "bg-yellow-50 dark:bg-yellow-900/20", border: "border-yellow-200 dark:border-yellow-800", text: "text-yellow-700 dark:text-yellow-300" }; default: return { bg: "bg-gray-50 dark:bg-gray-800", border: "border-gray-200 dark:border-gray-700", text: "text-gray-700 dark:text-gray-300" }; } }; // Get badge colors based on status const getStatusColors = (status: LeaveStatus) => { switch (status) { case "Approved": return { bg: "bg-green-100 dark:bg-green-900/30", text: "text-green-700 dark:text-green-300", icon: }; case "Rejected": return { bg: "bg-red-100 dark:bg-red-900/30", text: "text-red-700 dark:text-red-300", icon: }; case "Cancelled": return { bg: "bg-gray-100 dark:bg-gray-800", text: "text-gray-700 dark:text-gray-400", icon: }; case "Pending": default: return { bg: "bg-amber-100 dark:bg-amber-900/30", text: "text-amber-700 dark:text-amber-300", icon: }; } }; const typeColors = getTypeColors(leave.type); const statusColors = getStatusColors(leave.status); return (

{leave.compOff ? "Comp Off Request" : `${leave.type} Leave`}

Applied on {format(new Date(leave.appliedOn), "MMM d, yyyy")}

{statusColors.icon} {leave.status}
{format(startDate, "MMM d")} - {format(endDate, "MMM d, yyyy")} {durationDays} {durationDays === 1 ? "day" : "days"}
{leave.reason}
{leave.actionDate && (
{leave.status === "Approved" ? "Approved" : leave.status === "Rejected" ? "Rejected" : "Updated"} on {format(new Date(leave.actionDate), "MMM d, yyyy")} {leave.updatedByName && (leave.status === "Approved" || leave.status === "Rejected") && ( by {leave.updatedByName} )}
)} {!leave.actionDate && leave.updatedByName && (leave.status === "Approved" || leave.status === "Rejected") && (
{leave.status === "Approved" ? "Approved" : "Rejected"} by {leave.updatedByName}
)}
{leave.status === "Pending" && onCancel && ( )}
); } function renderLeavesList() { // Keep the original function for backwards compatibility if (isLoading) { return (
); } if (apiError) { return (

API Connection Error

{apiError}

); } if (filteredLeaves.length === 0) { return (

No leaves found

No leave records found for the selected filters

); } return (
{filteredLeaves.map((leave) => ( { setSelectedLeave(leave); setShowLeaveDetailDialog(true); }} onCancel={handleCancelLeave} /> ))}
); } // Add Upcoming Holidays component function UpcomingHolidays() { const [holidays, setHolidays] = useState([]); const [isLoading, setIsLoading] = useState(true); useEffect(() => { async function fetchHolidays() { setIsLoading(true); try { const allHolidays = await holidayApi.getAll(); // Filter for upcoming holidays only (starting from today) const today = new Date(); today.setHours(0, 0, 0, 0); const upcomingHolidays = allHolidays .filter(holiday => { const holidayDate = new Date(holiday.date); return holidayDate >= today; }) .sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()) .slice(0, 5); // Show only the next 5 holidays setHolidays(upcomingHolidays); } catch (error) { console.error("Error fetching holidays:", error); } finally { setIsLoading(false); } } fetchHolidays(); }, []); // Helper function to get seasonal color based on date const getSeasonalColor = (dateStr: string): string => { const date = new Date(dateStr); const month = date.getMonth(); // Winter (December, January, February) if (month === 11 || month === 0 || month === 1) return "bg-blue-50 border-blue-200 text-blue-700 dark:bg-blue-900/30 dark:border-blue-800 dark:text-blue-300"; // Spring (March, April, May) if (month >= 2 && month <= 4) return "bg-green-50 border-green-200 text-green-700 dark:bg-green-900/30 dark:border-green-800 dark:text-green-300"; // Summer (June, July, August) if (month >= 5 && month <= 7) return "bg-amber-50 border-amber-200 text-amber-700 dark:bg-amber-900/30 dark:border-amber-800 dark:text-amber-300"; // Fall (September, October, November) return "bg-orange-50 border-orange-200 text-orange-700 dark:bg-orange-900/30 dark:border-orange-800 dark:text-orange-300"; }; if (isLoading) { return (
); } if (holidays.length === 0) { return (

No upcoming holidays found

); } return (
{holidays.map(holiday => { const holidayDate = new Date(holiday.date); const isRecurring = holiday.isRecurring; const colorClass = getSeasonalColor(holiday.date); return (

{holiday.name}

{format(holidayDate, "EEEE, MMMM d, yyyy")} {isRecurring && (Recurring)}

{format(holidayDate, "MMM d")}
{holiday.description && (

{holiday.description}

)}
); })}
); } }; export default LeavesPage;