pmtool / src /pages /leave-types.tsx
devarshia5's picture
Upload 487 files
d97b8f9 verified
Raw
History Blame Contribute Delete
38.8 kB
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<MultiSelectRolesProps> = ({
roles,
selectedRoleIds,
onChange,
label = "Roles",
placeholder = "Select roles..."
}) => {
const [isOpen, setIsOpen] = useState(false);
const [searchTerm, setSearchTerm] = useState("");
const containerRef = useRef<HTMLDivElement>(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 (
<div className="space-y-2">
{label && <Label>{label}</Label>}
<div ref={containerRef} className="relative">
<div
className={cn(
"flex min-h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background cursor-pointer",
isOpen && "ring-2 ring-ring ring-offset-2"
)}
onClick={() => setIsOpen(!isOpen)}
>
{selectedRoleIds.length > 0 ? (
<div className="flex-1 truncate">{getSelectedRoleNames()}</div>
) : (
<div className="text-muted-foreground">{placeholder}</div>
)}
<ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
</div>
{isOpen && (
<div className="absolute z-50 mt-1 max-h-60 w-full overflow-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md">
<div className="sticky top-0 bg-popover p-2 border-b">
<Input
placeholder="Search roles..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="h-8"
onClick={(e) => e.stopPropagation()}
/>
</div>
<div className="pt-1">
{filteredRoles.length === 0 ? (
<div className="py-2 px-2 text-sm text-center text-muted-foreground">
No roles found
</div>
) : (
filteredRoles.map((role) => (
<div
key={role.roleId}
className={cn(
"flex items-center justify-between py-1.5 px-2 text-sm rounded-sm cursor-pointer",
selectedRoleIds.includes(role.roleId)
? "bg-primary text-primary-foreground"
: "hover:bg-accent hover:text-accent-foreground"
)}
onClick={(e) => {
e.stopPropagation();
toggleRole(role.roleId);
}}
>
<span>{role.roleName}</span>
{selectedRoleIds.includes(role.roleId) && (
<Check className="h-4 w-4" />
)}
</div>
))
)}
</div>
</div>
)}
{selectedRoleIds.length > 0 && (
<div className="flex flex-wrap gap-1 mt-2">
{selectedRoleIds.map(roleId => {
const role = roles.find(r => r.roleId === roleId);
if (!role) return null;
return (
<Badge
key={roleId}
variant="secondary"
className="flex items-center gap-1"
>
{role.roleName}
<button
onClick={(e) => {
e.stopPropagation();
toggleRole(roleId);
}}
className="ml-1 rounded-full hover:bg-accent p-0.5"
>
<X className="h-3 w-3" />
<span className="sr-only">Remove {role.roleName}</span>
</button>
</Badge>
);
})}
</div>
)}
</div>
{selectedRoleIds.length === 0 && (
<p className="text-xs text-muted-foreground">
Leave empty to apply to all roles
</p>
)}
</div>
);
};
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<LeaveMaster[]>([]);
const [filteredLeaveTypes, setFilteredLeaveTypes] = useState<LeaveMaster[]>([]);
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<LeaveMaster | null>(null);
// Form state
const [newLeaveType, setNewLeaveType] = useState<LeaveMasterCreateRequest>({
leaveType: "",
description: "",
defaultDays: 0,
carryForwardLimit: 0,
isActive: true,
minDaysBeforeApply: 0,
requiresApproval: true,
requiresDocumentation: false,
applyToRoles: "",
});
const [editLeaveType, setEditLeaveType] = useState<LeaveMasterUpdateRequest>({
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<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
) => {
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<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
) => {
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<HTMLInputElement>,
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<number[]>([]);
const [editSelectedRoleIds, setEditSelectedRoleIds] = useState<number[]>([]);
// 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 (
<div className="flex min-h-screen flex-col">
<Header toggleSidebar={toggleSidebar} isSidebarOpen={isSidebarOpen} />
<Sidebar isOpen={isSidebarOpen} setIsOpen={setIsSidebarOpen} />
<main className={`flex-1 p-6 pt-20 md:p-10 md:pt-24 ${isSidebarOpen ? "md:ml-64" : ""} transition-all duration-300`}>
<div className="mx-auto max-w-6xl">
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between mb-8 gap-4">
<div>
<h2 className="text-3xl font-bold tracking-tight">Leave Types</h2>
<p className="text-muted-foreground">
Manage leave types and policies
</p>
</div>
<Button onClick={(e) => {
e.stopPropagation();
setIsCreateDialogOpen(true);
}}>
<PlusCircle className="mr-2 h-4 w-4" />
Add Leave Type
</Button>
<CustomDialog
isOpen={isCreateDialogOpen}
onClose={() => setIsCreateDialogOpen(false)}
title="Add Leave Type"
description="Create a new leave type and define its policies"
allowClose={true}
isPending={false}
>
<form onSubmit={handleCreateSubmit} className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="leaveType">Leave Type Name</Label>
<Input
id="leaveType"
name="leaveType"
placeholder="Annual Leave"
value={newLeaveType.leaveType}
onChange={handleCreateChange}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="defaultDays">Default Days Per Year</Label>
<Input
id="defaultDays"
name="defaultDays"
type="number"
min="0"
placeholder="21"
value={newLeaveType.defaultDays}
onChange={(e) => handleNumericChange(e, false)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="carryForwardLimit">Carry Forward Limit</Label>
<Input
id="carryForwardLimit"
name="carryForwardLimit"
type="number"
min="0"
placeholder="5"
value={newLeaveType.carryForwardLimit}
onChange={(e) => handleNumericChange(e, false)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="minDaysBeforeApply">
Minimum Days Before Apply
</Label>
<Input
id="minDaysBeforeApply"
name="minDaysBeforeApply"
type="number"
min="0"
placeholder="3"
value={newLeaveType.minDaysBeforeApply}
onChange={(e) => handleNumericChange(e, false)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="maxConsecutiveDays">
Maximum Consecutive Days (Optional)
</Label>
<Input
id="maxConsecutiveDays"
name="maxConsecutiveDays"
type="number"
min="0"
placeholder="30"
value={newLeaveType.maxConsecutiveDays || ""}
onChange={(e) =>
handleNumericChange(e, false)
}
/>
</div>
<div className="space-y-2">
<MultiSelectRoles
roles={roles || []}
selectedRoleIds={selectedRoleIds}
onChange={handleRoleSelectionChange}
label="Apply To Roles"
placeholder="Select roles..."
/>
</div>
<div className="space-y-2 flex items-center">
<div className="flex items-center space-x-2">
<Switch
id="isActive"
checked={newLeaveType.isActive}
onCheckedChange={(checked) =>
handleCreateSwitchChange("isActive", checked)
}
/>
<Label htmlFor="isActive">Active</Label>
</div>
</div>
<div className="space-y-2 flex items-center">
<div className="flex items-center space-x-2">
<Switch
id="requiresApproval"
checked={newLeaveType.requiresApproval}
onCheckedChange={(checked) =>
handleCreateSwitchChange("requiresApproval", checked)
}
/>
<Label htmlFor="requiresApproval">Requires Approval</Label>
</div>
</div>
<div className="space-y-2 flex items-center">
<div className="flex items-center space-x-2">
<Switch
id="requiresDocumentation"
checked={newLeaveType.requiresDocumentation}
onCheckedChange={(checked) =>
handleCreateSwitchChange("requiresDocumentation", checked)
}
/>
<Label htmlFor="requiresDocumentation">Requires Documentation</Label>
</div>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="description">Description</Label>
<Textarea
id="description"
name="description"
placeholder="Annual leave entitlement for employees"
value={newLeaveType.description}
onChange={handleCreateChange}
className="min-h-[100px]"
required
/>
</div>
<div className="flex justify-end gap-2 mt-4">
<Button
variant="outline"
type="button"
onClick={() => {
resetCreateForm();
setIsCreateDialogOpen(false);
}}
>
Cancel
</Button>
<Button type="submit">Create Leave Type</Button>
</div>
</form>
</CustomDialog>
<CustomDialog
isOpen={isEditDialogOpen}
onClose={() => setIsEditDialogOpen(false)}
title="Edit Leave Type"
description="Update leave type and its policies"
allowClose={true}
isPending={false}
>
<form onSubmit={handleEditSubmit} className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="edit-leaveType">Leave Type Name</Label>
<Input
id="edit-leaveType"
name="leaveType"
placeholder="Annual Leave"
value={editLeaveType.leaveType}
onChange={handleEditChange}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="edit-defaultDays">Default Days Per Year</Label>
<Input
id="edit-defaultDays"
name="defaultDays"
type="number"
min="0"
placeholder="21"
value={editLeaveType.defaultDays}
onChange={(e) => handleNumericChange(e, true)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="edit-carryForwardLimit">Carry Forward Limit</Label>
<Input
id="edit-carryForwardLimit"
name="carryForwardLimit"
type="number"
min="0"
placeholder="5"
value={editLeaveType.carryForwardLimit}
onChange={(e) => handleNumericChange(e, true)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="edit-minDaysBeforeApply">
Minimum Days Before Apply
</Label>
<Input
id="edit-minDaysBeforeApply"
name="minDaysBeforeApply"
type="number"
min="0"
placeholder="3"
value={editLeaveType.minDaysBeforeApply}
onChange={(e) => handleNumericChange(e, true)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="edit-maxConsecutiveDays">
Maximum Consecutive Days (Optional)
</Label>
<Input
id="edit-maxConsecutiveDays"
name="maxConsecutiveDays"
type="number"
min="0"
placeholder="30"
value={editLeaveType.maxConsecutiveDays || ""}
onChange={(e) =>
handleNumericChange(e, true)
}
/>
</div>
<div className="space-y-2">
<MultiSelectRoles
roles={roles || []}
selectedRoleIds={editSelectedRoleIds}
onChange={handleEditRoleSelectionChange}
label="Apply To Roles"
placeholder="Select roles..."
/>
</div>
<div className="space-y-2 flex items-center">
<div className="flex items-center space-x-2">
<Switch
id="edit-isActive"
checked={editLeaveType.isActive}
onCheckedChange={(checked) =>
handleEditSwitchChange("isActive", checked)
}
/>
<Label htmlFor="edit-isActive">Active</Label>
</div>
</div>
<div className="space-y-2 flex items-center">
<div className="flex items-center space-x-2">
<Switch
id="edit-requiresApproval"
checked={editLeaveType.requiresApproval}
onCheckedChange={(checked) =>
handleEditSwitchChange("requiresApproval", checked)
}
/>
<Label htmlFor="edit-requiresApproval">Requires Approval</Label>
</div>
</div>
<div className="space-y-2 flex items-center">
<div className="flex items-center space-x-2">
<Switch
id="edit-requiresDocumentation"
checked={editLeaveType.requiresDocumentation}
onCheckedChange={(checked) =>
handleEditSwitchChange("requiresDocumentation", checked)
}
/>
<Label htmlFor="edit-requiresDocumentation">Requires Documentation</Label>
</div>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="edit-description">Description</Label>
<Textarea
id="edit-description"
name="description"
placeholder="Annual leave entitlement for employees"
value={editLeaveType.description}
onChange={handleEditChange}
className="min-h-[100px]"
required
/>
</div>
<div className="flex justify-end gap-2 mt-4">
<Button
variant="outline"
type="button"
onClick={() => {
setIsEditDialogOpen(false);
setSelectedLeaveType(null);
}}
>
Cancel
</Button>
<Button type="submit">Update Leave Type</Button>
</div>
</form>
</CustomDialog>
<CustomDialog
isOpen={isDeleteDialogOpen}
onClose={() => setIsDeleteDialogOpen(false)}
title="Delete Leave Type"
description="Are you sure you want to delete this leave type? This action cannot be undone."
allowClose={true}
isPending={false}
>
{selectedLeaveType && (
<div className="py-4">
<p className="font-medium">{selectedLeaveType.leaveType}</p>
<p className="text-sm text-muted-foreground mt-1">
{selectedLeaveType.description}
</p>
</div>
)}
<div className="flex justify-end gap-2 mt-4">
<Button
variant="outline"
onClick={() => {
setIsDeleteDialogOpen(false);
setSelectedLeaveType(null);
}}
>
Cancel
</Button>
<Button variant="destructive" onClick={handleDeleteConfirm}>
Delete
</Button>
</div>
</CustomDialog>
</div>
<Card>
<CardHeader>
<CardTitle>All Leave Types</CardTitle>
<CardDescription>
Configure leave types, balances, and approval policies
</CardDescription>
<div className="flex items-center gap-4 mt-4">
<div className="relative flex-1">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search leave types..."
className="pl-8"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
</div>
</CardHeader>
<CardContent>
{isLoading ? (
<div className="flex items-center justify-center p-8">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
</div>
) : (
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead>Leave Type</TableHead>
<TableHead>Description</TableHead>
<TableHead>Balance (Days)</TableHead>
<TableHead>Carry Forward</TableHead>
<TableHead>Applies To</TableHead>
<TableHead>Status</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredLeaveTypes.length === 0 ? (
<TableRow>
<TableCell
colSpan={7}
className="h-24 text-center"
>
No leave types found.
</TableCell>
</TableRow>
) : (
filteredLeaveTypes.map((leaveType) => (
<TableRow key={leaveType.id}>
<TableCell className="font-medium">
{leaveType.leaveType}
</TableCell>
<TableCell className="max-w-xs truncate">
{leaveType.description}
</TableCell>
<TableCell>{leaveType.defaultDays} days</TableCell>
<TableCell>{leaveType.carryForwardLimit} days</TableCell>
<TableCell>
{formatRoles(leaveType.applyToRoles)}
</TableCell>
<TableCell>
<Badge
variant={leaveType.isActive ? "default" : "outline"}
className="flex w-fit items-center gap-1"
>
{leaveType.isActive ?
<Check className="h-3 w-3"/> :
<X className="h-3 w-3"/>
}
<span>{leaveType.isActive ? "Active" : "Inactive"}</span>
</Badge>
</TableCell>
<TableCell className="text-right">
<div className="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onClick={() => handleEditClick(leaveType)}
>
<FileEdit className="h-4 w-4" />
</Button>
<Button
variant={leaveType.isActive ? "outline" : "default"}
size="sm"
onClick={() => handleToggleStatus(leaveType)}
>
{leaveType.isActive ?
<X className="h-4 w-4"/> :
<Check className="h-4 w-4"/>
}
</Button>
<Button
variant="destructive"
size="sm"
onClick={() => handleDeleteClick(leaveType)}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
)}
</CardContent>
</Card>
</div>
</main>
</div>
);
};
export default LeaveTypesPage;