import React, { useState, useEffect, useRef } from "react"; import { useAuth } from "@/lib/auth-context"; import { format, getYear, parseISO, differenceInCalendarDays, getMonth } from "date-fns"; import * as XLSX from 'xlsx'; import { Calendar as CalendarIcon, CheckCircle, XCircle, Loader2, Filter, SearchIcon, X, Download, Users, UserPlus, FileText, BarChart, ChevronDown, Check, Clock, User, AlertCircle, File, ChevronLeft, ChevronRight, ListFilter, SlidersHorizontal, Table as TableIcon, UserIcon, Info, FileSpreadsheet, } 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 { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Separator } from "@/components/ui/separator"; import { toast } from "sonner"; import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Leave, LeaveStatus, LeaveType, LeaveStatusUpdateRequest, Employee, LeaveMaster, LeaveBalance } from "@/types"; import { leaveApi } from "@/services/leaveApi"; import { employeeApi } from "@/services/employeeApi"; import { leaveMasterApi } from "@/services/leaveMasterApi"; import { leaveBalanceApi } from "@/services/leaveBalanceApi"; import LeaveCard from "@/components/leaves/LeaveCard"; 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 { cn } from "@/lib/utils"; import CustomDialog from "@/components/CustomDialog"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Textarea } from "@/components/ui/textarea"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; import { useToast } from "@/components/ui/use-toast"; import Layout from "@/components/layout/layout"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import LeaveList from "@/components/leaves/LeaveList"; import { Calendar as CalendarComponent } from "@/components/ui/calendar"; import MonthlyReportTab from "./leaves-admin-monthly-report-tab"; import CalendarTab from "./leaves-admin-calendar-tab"; // Custom Dropdown Component interface CustomDropdownProps { value: string; onChange: (value: string) => void; options: { value: string; label: string; color?: string }[]; placeholder: string; showBadge?: boolean; disabled?: boolean; searchable?: boolean; } function CustomDropdown({ value, onChange, options, placeholder, showBadge = false, disabled = false, searchable = false }: CustomDropdownProps) { const [open, setOpen] = useState(false); const [searchQuery, setSearchQuery] = useState(""); const getLabel = () => { return options.find(option => option.value === value)?.label || placeholder; }; const getColor = () => { return options.find(option => option.value === value)?.color || ""; }; const handleButtonClick = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); if (!disabled) { setOpen(!open); } }; const handleOptionClick = (optionValue: string) => { onChange(optionValue); setOpen(false); setSearchQuery(""); }; const filteredOptions = searchable && searchQuery ? options.filter(option => option.label.toLowerCase().includes(searchQuery.toLowerCase()) ) : options; return (
{showBadge && value ? (
{getLabel()}
) : ( {getLabel()} )}
{open && (
{searchable && (
setSearchQuery(e.target.value)} className="pl-8 h-9" onClick={(e) => e.stopPropagation()} />
)}
{filteredOptions.length > 0 ? ( filteredOptions.map((option) => (
handleOptionClick(option.value)} > {showBadge ? ( {option.label} ) : ( option.label )} {value === option.value && }
)) ) : (
No results found
)}
)}
); } // Type for leave with employee info interface LeaveWithEmployee extends Leave { employeeName?: string; } // Type for monthly leave report interface EmployeeLeaveReport { employeeId: number; employeeName: string; employeeCode: string; halfDay: { count: number; dates: number[] }; annualLeave: { count: number; dates: number[] }; compensatoryOff: { count: number; dates: number[] }; workFromHome: { count: number; dates: number[] }; sickLeave: { count: number; dates: number[] }; casualLeave: { count: number; dates: number[] }; other: { count: number; dates: number[] }; } // Define the form schema for status update const statusUpdateSchema = z.object({ comments: z.string().optional(), }); type StatusUpdateFormValues = z.infer; // Custom LeaveDetailDialog component function CustomLeaveDetailDialog({ leave, employee, approver, isOpen, onClose, onDownloadDocument, leaveMasters, getLeaveTypeName, }: { leave: Leave | null; employee: Employee | null; approver: Employee | null; isOpen: boolean; onClose: () => void; onDownloadDocument?: (leave: Leave) => void; leaveMasters: LeaveMaster[]; getLeaveTypeName: (id?: number) => string; }) { if (!leave) return null; const [leaveBalances, setLeaveBalances] = useState([]); const [loadingBalances, setLoadingBalances] = useState(false); useEffect(() => { const fetchBalances = async () => { if (leave && isOpen && leave.employeeId) { setLoadingBalances(true); try { const year = getYear(new Date(leave.startDate)); const balances = await leaveBalanceApi.getByEmployeeIdAndYear(leave.employeeId, year); setLeaveBalances(balances); } catch (error) { console.error("Failed to fetch leave balances", error); } finally { setLoadingBalances(false); } } }; fetchBalances(); }, [leave, isOpen]); const startDate = new Date(leave.startDate); const endDate = new Date(leave.endDate); const durationDays = differenceInCalendarDays(endDate, startDate) + 1; // Find the associated leave master if available const leaveMaster = leave.leaveMasterId ? leaveMasters.find(m => m.id === leave.leaveMasterId) : null; // Helper function to get status icon const getStatusIcon = (status: LeaveStatus) => { switch (status) { case "Approved": return ; case "Rejected": return ; case "Cancelled": return ; case "Pending": default: return ; } }; // Helper function to get badge variant const getStatusBadgeVariant = (status: LeaveStatus) => { switch (status) { case "Approved": return "bg-green-500 hover:bg-green-600"; case "Rejected": return "bg-red-500 hover:bg-red-600"; case "Cancelled": return "bg-gray-500 hover:bg-gray-600"; case "Pending": default: return "bg-yellow-500 hover:bg-yellow-600"; } }; return (
{getStatusIcon(leave.status)} {leave.status}
Employee:
{employee ? `${employee.firstName} ${employee.lastName}` : `ID: ${leave.employeeId}`}
Period:
{format(startDate, "PPP")} to {format(endDate, "PPP")} {" "}({durationDays} {durationDays === 1 ? "day" : "days"})
Type:
{leave.leaveMasterId ? getLeaveTypeName(leave.leaveMasterId) : leave.type}
{leaveMaster && (
Leave Policy:
{leaveMaster.description && (
{leaveMaster.description}
)} {leaveMaster.requiresDocumentation && (
Documentation required
)} {leaveMaster.minDaysBeforeApply > 0 && (
Must apply {leaveMaster.minDaysBeforeApply} days in advance
)}
)}
Reason:
{leave.reason}
{leave.appliedOn && (
Applied On:
{format(new Date(leave.appliedOn), "PPP")}
)} {leave.halfDay && (
Half Day:
Yes
)} {leave.documents && (
Documents:
)} {(leave.status === "Approved" || leave.status === "Rejected") && ( <> {approver && (
Actioned By:
{approver.firstName} {approver.lastName}
)} {leave.actionDate && (
Action Date:
{format(new Date(leave.actionDate), "PPP")}
)} {leave.comments && (
Comments:
{leave.comments}
)} )}

Leave Balances ({leave ? getYear(new Date(leave.startDate)) : ''})

{loadingBalances ? (
) : leaveBalances.length > 0 ? (
Type Allocated Used Balance {leaveBalances.map((balance) => ( {balance.leaveType} {balance.allocated} {balance.used} {balance.pending} ))}
) : (

No leave balance data available.

)}
); } function CustomLeaveStatusUpdateDialog({ leave, action, isOpen, onClose, onSubmit, employeeName, approverId, isSubmitting, leaveMasters, getLeaveTypeName, }: { leave: Leave | null; action: "approve" | "reject"; isOpen: boolean; onClose: () => void; onSubmit: (data: LeaveStatusUpdateRequest) => Promise; employeeName?: string; approverId: number; isSubmitting: boolean; leaveMasters: LeaveMaster[]; getLeaveTypeName: (id?: number) => string; }) { const form = useForm({ resolver: zodResolver(statusUpdateSchema), defaultValues: { comments: "", }, }); if (!leave) return null; const handleSubmit = async (values: StatusUpdateFormValues) => { try { const updateData: LeaveStatusUpdateRequest = { id: leave.id, status: action === "approve" ? "Approved" : "Rejected", approvedById: approverId, actionDate: format(new Date(), "yyyy-MM-dd'T'HH:mm:ss"), comments: values.comments, }; await onSubmit(updateData); form.reset(); onClose(); } catch (error) { console.error(`Error ${action} leave request:`, error); toast.error(`Failed to ${action} leave request`); } }; // Only treat as Comp Off Request if compOff flag is explicitly true // If leave type is Compensatory but compOff is false, it's a regular leave request const isCompOff = leave.compOff === true; const requestType = isCompOff ? "Comp Off Request" : "Leave Request"; return (
Employee:
{employeeName ? employeeName : `ID: ${leave.employeeId}`}
Date:
{format(new Date(leave.startDate), "PPP")} to {format(new Date(leave.endDate), "PPP")}
Type:
{leave.leaveMasterId ? getLeaveTypeName(leave.leaveMasterId) : leave.type}
Reason:
{leave.reason}
( Comments