import React, { useState, useEffect, useRef } from "react"; import { useLocation } from "react-router-dom"; import { useAuth } from "@/lib/auth-context"; import { PlusCircle, Search, Loader2, FileEdit, Trash2, Check, X, CalendarRange, ChevronsUpDown } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { toast } from "sonner"; import { LeaveMaster, LeaveMasterCreateRequest, LeaveMasterUpdateRequest } from "@/types"; import { leaveMasterApi } from "@/services/leaveMasterApi"; import { roleService } from "@/lib/api"; import Header from "@/components/layout/header"; import Sidebar from "@/components/layout/sidebar"; import { useIsMobile } from "@/hooks/use-mobile"; import CustomDialog from "@/components/CustomDialog"; import { cn } from "@/lib/utils"; // Multi-select roles component interface Role { roleId: number; roleName: string; } interface MultiSelectRolesProps { roles: Role[]; selectedRoleIds: number[]; onChange: (roleIds: number[]) => void; label?: string; placeholder?: string; } const MultiSelectRoles: React.FC = ({ roles, selectedRoleIds, onChange, label = "Roles", placeholder = "Select roles..." }) => { const [isOpen, setIsOpen] = useState(false); const [searchTerm, setSearchTerm] = useState(""); const containerRef = useRef(null); // Close dropdown when clicking outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (containerRef.current && !containerRef.current.contains(event.target as Node)) { setIsOpen(false); } }; document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); // Filter roles based on search term const filteredRoles = roles.filter(role => role.roleName.toLowerCase().includes(searchTerm.toLowerCase()) ); // Toggle role selection const toggleRole = (roleId: number) => { if (selectedRoleIds.includes(roleId)) { onChange(selectedRoleIds.filter(id => id !== roleId)); } else { onChange([...selectedRoleIds, roleId]); } }; // Get selected role names for display const getSelectedRoleNames = () => { return selectedRoleIds .map(id => roles.find(role => role.roleId === id)?.roleName) .filter(Boolean) .join(", "); }; return (
{label && }
setIsOpen(!isOpen)} > {selectedRoleIds.length > 0 ? (
{getSelectedRoleNames()}
) : (
{placeholder}
)}
{isOpen && (
setSearchTerm(e.target.value)} className="h-8" onClick={(e) => e.stopPropagation()} />
{filteredRoles.length === 0 ? (
No roles found
) : ( filteredRoles.map((role) => (
{ e.stopPropagation(); toggleRole(role.roleId); }} > {role.roleName} {selectedRoleIds.includes(role.roleId) && ( )}
)) )}
)} {selectedRoleIds.length > 0 && (
{selectedRoleIds.map(roleId => { const role = roles.find(r => r.roleId === roleId); if (!role) return null; return ( {role.roleName} ); })}
)}
{selectedRoleIds.length === 0 && (

Leave empty to apply to all roles

)}
); }; const LeaveTypesPage: React.FC = () => { const { userData } = useAuth(); const location = useLocation(); const isMobile = useIsMobile(); 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 [leaveTypes, setLeaveTypes] = useState([]); const [filteredLeaveTypes, setFilteredLeaveTypes] = useState([]); const [searchQuery, setSearchQuery] = useState(""); const [roles, setRoles] = useState<{roleId: number, roleName: string}[]>([]); // Dialog states const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false); const [isEditDialogOpen, setIsEditDialogOpen] = useState(false); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const [selectedLeaveType, setSelectedLeaveType] = useState(null); // Form state const [newLeaveType, setNewLeaveType] = useState({ leaveType: "", description: "", defaultDays: 0, carryForwardLimit: 0, isActive: true, minDaysBeforeApply: 0, requiresApproval: true, requiresDocumentation: false, applyToRoles: "", }); const [editLeaveType, setEditLeaveType] = useState({ id: 0, leaveType: "", description: "", defaultDays: 0, carryForwardLimit: 0, isActive: true, minDaysBeforeApply: 0, requiresApproval: true, requiresDocumentation: false, applyToRoles: "", }); // Toggle sidebar const toggleSidebar = () => { setIsSidebarOpen(!isSidebarOpen); }; // Fetch leave types useEffect(() => { const fetchLeaveTypes = async () => { try { setIsLoading(true); const leaveTypes = await leaveMasterApi.getAll(); setLeaveTypes(leaveTypes); setFilteredLeaveTypes(leaveTypes); } catch (error) { console.error("Error fetching leave types:", error); toast.error("Failed to fetch leave types"); } finally { setIsLoading(false); } }; const fetchRoles = async () => { try { const roles = await roleService.getAll(); setRoles(roles); } catch (error) { console.error("Error fetching roles:", error); toast.error("Failed to fetch roles"); } }; fetchLeaveTypes(); fetchRoles(); }, []); // Filter leave types when search query changes useEffect(() => { if (!searchQuery) { setFilteredLeaveTypes(leaveTypes); return; } const query = searchQuery.toLowerCase(); const filtered = leaveTypes.filter( (type) => type.leaveType.toLowerCase().includes(query) || type.description.toLowerCase().includes(query) ); setFilteredLeaveTypes(filtered); }, [searchQuery, leaveTypes]); // Handle create form submission const handleCreateSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { const createdType = await leaveMasterApi.create(newLeaveType); setLeaveTypes([...leaveTypes, createdType]); setIsCreateDialogOpen(false); resetCreateForm(); toast.success("Leave type created successfully"); } catch (error) { console.error("Error creating leave type:", error); toast.error("Failed to create leave type"); } }; // Handle edit form submission const handleEditSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!selectedLeaveType) return; try { const updatedType = await leaveMasterApi.update( selectedLeaveType.id, editLeaveType ); // Check if the response is not null (i.e., it's not a 204 No Content response) if (updatedType) { setLeaveTypes( leaveTypes.map((type) => type.id === updatedType.id ? updatedType : type ) ); } else { // Handle the case when there's no content (204 No Content) console.log('Leave type update successful, but no data returned.'); // Optionally handle this case differently, depending on your requirements } setIsEditDialogOpen(false); setSelectedLeaveType(null); toast.success("Leave type updated successfully"); } catch (error) { console.error("Error updating leave type:", error); toast.error("Failed to update leave type"); } }; // Handle delete confirmation const handleDeleteConfirm = async () => { if (!selectedLeaveType) return; try { await leaveMasterApi.delete(selectedLeaveType.id); setLeaveTypes(leaveTypes.filter((type) => type.id !== selectedLeaveType.id)); setIsDeleteDialogOpen(false); setSelectedLeaveType(null); toast.success("Leave type deleted successfully"); } catch (error) { console.error("Error deleting leave type:", error); toast.error("Failed to delete leave type"); } }; // Toggle leave type active status const handleToggleStatus = async (leaveType: LeaveMaster) => { try { const updatedType = await leaveMasterApi.toggleStatus( leaveType.id, !leaveType.isActive ); setLeaveTypes( leaveTypes.map((type) => type.id === updatedType.id ? updatedType : type ) ); toast.success(`Leave type ${updatedType.isActive ? "activated" : "deactivated"} successfully`); } catch (error) { console.error("Error toggling leave type status:", error); toast.error("Failed to update leave type status"); } }; // Set up edit form const handleEditClick = (leaveType: LeaveMaster) => { setSelectedLeaveType(leaveType); setEditLeaveType({ id: leaveType.id, leaveType: leaveType.leaveType, description: leaveType.description, defaultDays: leaveType.defaultDays, carryForwardLimit: leaveType.carryForwardLimit, isActive: leaveType.isActive, maxConsecutiveDays: leaveType.maxConsecutiveDays, minDaysBeforeApply: leaveType.minDaysBeforeApply, requiresApproval: leaveType.requiresApproval, requiresDocumentation: leaveType.requiresDocumentation, applyToRoles: leaveType.applyToRoles, }); setIsEditDialogOpen(true); }; // Set up delete dialog const handleDeleteClick = (leaveType: LeaveMaster) => { setSelectedLeaveType(leaveType); setIsDeleteDialogOpen(true); }; // Reset create form const resetCreateForm = () => { setNewLeaveType({ leaveType: "", description: "", defaultDays: 0, carryForwardLimit: 0, isActive: true, minDaysBeforeApply: 0, requiresApproval: true, requiresDocumentation: false, applyToRoles: "", }); }; // Handle form input changes for create const handleCreateChange = ( e: React.ChangeEvent ) => { const { name, value } = e.target; setNewLeaveType((prev) => ({ ...prev, [name]: value, })); }; // Handle create form switch changes const handleCreateSwitchChange = (name: string, checked: boolean) => { setNewLeaveType((prev) => ({ ...prev, [name]: checked, })); }; // Handle form input changes for edit const handleEditChange = ( e: React.ChangeEvent ) => { const { name, value } = e.target; setEditLeaveType((prev) => ({ ...prev, [name]: value, })); }; // Handle edit form switch changes const handleEditSwitchChange = (name: string, checked: boolean) => { setEditLeaveType((prev) => ({ ...prev, [name]: checked, })); }; // Handle numeric input to ensure they're numbers const handleNumericChange = ( e: React.ChangeEvent, isEditForm: boolean ) => { const { name, value } = e.target; const numValue = value === "" ? 0 : parseInt(value, 10); if (isEditForm) { setEditLeaveType((prev) => ({ ...prev, [name]: numValue, })); } else { setNewLeaveType((prev) => ({ ...prev, [name]: numValue, })); } }; // Format roles string for display const formatRoles = (rolesString: string) => { if (!rolesString || !roles.length) return "All roles"; const roleIds = rolesString.split(",").map(id => parseInt(id.trim(), 10)); const roleNames = roleIds.map( id => roles.find(r => r.roleId === id)?.roleName || `Role ${id}` ); return roleNames.join(", "); }; // Convert string role IDs to array of numbers const parseRoleIds = (rolesString: string): number[] => { if (!rolesString || rolesString.trim() === "") return []; return rolesString.split(',').map(id => parseInt(id.trim(), 10)); }; // Convert array of role IDs to comma-separated string const stringifyRoleIds = (roleIds: number[]): string => { return roleIds.join(','); }; // New state for multi-select const [selectedRoleIds, setSelectedRoleIds] = useState([]); const [editSelectedRoleIds, setEditSelectedRoleIds] = useState([]); // Update the Role IDs when form state changes useEffect(() => { setSelectedRoleIds(parseRoleIds(newLeaveType.applyToRoles)); }, [newLeaveType.applyToRoles]); useEffect(() => { setEditSelectedRoleIds(parseRoleIds(editLeaveType.applyToRoles)); }, [editLeaveType.applyToRoles]); // Handle role selection for create form const handleRoleSelectionChange = (roleIds: number[]) => { setSelectedRoleIds(roleIds); setNewLeaveType(prev => ({ ...prev, applyToRoles: stringifyRoleIds(roleIds) })); }; // Handle role selection for edit form const handleEditRoleSelectionChange = (roleIds: number[]) => { setEditSelectedRoleIds(roleIds); setEditLeaveType(prev => ({ ...prev, applyToRoles: stringifyRoleIds(roleIds) })); }; return (

Leave Types

Manage leave types and policies

setIsCreateDialogOpen(false)} title="Add Leave Type" description="Create a new leave type and define its policies" allowClose={true} isPending={false} >
handleNumericChange(e, false)} required />
handleNumericChange(e, false)} required />
handleNumericChange(e, false)} required />
handleNumericChange(e, false) } />
handleCreateSwitchChange("isActive", checked) } />
handleCreateSwitchChange("requiresApproval", checked) } />
handleCreateSwitchChange("requiresDocumentation", checked) } />