pmtool / src /components /leaves /LeaveList.tsx
devarshia5's picture
Upload 487 files
d97b8f9 verified
Raw
History Blame Contribute Delete
12.9 kB
import React, { useState, useEffect } from 'react';
import {
Box,
Card,
CardContent,
Typography,
Grid,
Divider,
Chip,
Stack,
Pagination,
CircularProgress,
Select,
MenuItem,
FormControl,
InputLabel,
TextField,
Button,
IconButton,
SelectChangeEvent,
Alert,
} from '@mui/material';
import FilterListIcon from '@mui/icons-material/FilterList';
import VisibilityIcon from '@mui/icons-material/Visibility';
import { subYears, getYear } from 'date-fns';
import { Leave, LeaveStatus } from '../../types/Leave';
import LeaveCard from './LeaveCard';
import LeaveDetail from './LeaveDetail';
import LeaveStatusUpdate from './LeaveStatusUpdate';
// Extend the Leave type to include additional properties needed
interface ExtendedLeave extends Leave {
employeeName?: string;
leaveMasterId?: number;
appliedOn?: string; // To match with imported type
}
export interface LeaveListProps {
leaves: ExtendedLeave[];
isLoading?: boolean;
error?: string | null;
onStatusUpdate?: (leaveId: string, status: LeaveStatus, comment?: string) => Promise<void>;
canApprove?: boolean;
title?: string;
showFilters?: boolean;
leaveTypes?: string[];
employees?: { id: string; firstName: string; lastName: string }[];
getLeaveTypeName?: (id?: number) => string;
}
const LeaveList: React.FC<LeaveListProps> = ({
leaves,
isLoading = false,
error = null,
onStatusUpdate,
canApprove = false,
title = "Leave Requests",
showFilters = true,
leaveTypes = [],
employees = [],
getLeaveTypeName,
}) => {
const [filteredLeaves, setFilteredLeaves] = useState<ExtendedLeave[]>(leaves);
const [statusFilter, setStatusFilter] = useState<string>("All");
const [typeFilter, setTypeFilter] = useState<string>("All");
const [employeeFilter, setEmployeeFilter] = useState<string>("All");
const [yearFilter, setYearFilter] = useState<number>(getYear(new Date()));
const [searchTerm, setSearchTerm] = useState<string>("");
const [displayFilters, setDisplayFilters] = useState<boolean>(false);
const [page, setPage] = useState<number>(1);
const [selectedLeave, setSelectedLeave] = useState<ExtendedLeave | null>(null);
const [showDetailDialog, setShowDetailDialog] = useState<boolean>(false);
const [showApproveDialog, setShowApproveDialog] = useState<boolean>(false);
const [showRejectDialog, setShowRejectDialog] = useState<boolean>(false);
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
const itemsPerPage = 6;
const years = Array.from({ length: 5 }, (_, i) => getYear(subYears(new Date(), i)));
// Apply filters whenever filter values change
useEffect(() => {
if (!leaves) return;
let filtered = [...leaves];
// Filter by year
filtered = filtered.filter(leave => {
const leaveYear = getYear(new Date(leave.startDate));
return leaveYear === yearFilter;
});
// Filter by status if not "All"
if (statusFilter !== "All") {
filtered = filtered.filter(leave => leave.status === statusFilter);
}
// Filter by type if not "All"
if (typeFilter !== "All") {
filtered = filtered.filter(leave => {
if (leave.type) return leave.type === typeFilter;
return false;
});
}
// Filter by employee if not "All"
if (employeeFilter !== "All") {
filtered = filtered.filter(leave => leave.employeeId === employeeFilter);
}
// Filter by search term
if (searchTerm) {
filtered = filtered.filter(leave =>
leave.reason.toLowerCase().includes(searchTerm.toLowerCase()) ||
leave.employeeName?.toLowerCase().includes(searchTerm.toLowerCase())
);
}
setFilteredLeaves(filtered);
setPage(1); // Reset to first page whenever filters change
}, [leaves, statusFilter, typeFilter, employeeFilter, yearFilter, searchTerm]);
const handlePageChange = (event: React.ChangeEvent<unknown>, value: number) => {
setPage(value);
};
const handleStatusFilterChange = (event: SelectChangeEvent) => {
setStatusFilter(event.target.value);
};
const handleTypeFilterChange = (event: SelectChangeEvent) => {
setTypeFilter(event.target.value);
};
const handleEmployeeFilterChange = (event: SelectChangeEvent) => {
setEmployeeFilter(event.target.value);
};
const handleYearFilterChange = (event: SelectChangeEvent) => {
setYearFilter(Number(event.target.value));
};
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(event.target.value);
};
const handleViewLeave = (leave: ExtendedLeave) => {
setSelectedLeave(leave);
setShowDetailDialog(true);
};
const handleApproveLeave = (leave: ExtendedLeave) => {
setSelectedLeave(leave);
setShowApproveDialog(true);
};
const handleRejectLeave = (leave: ExtendedLeave) => {
setSelectedLeave(leave);
setShowRejectDialog(true);
};
const handleSubmitStatusUpdate = async (status: LeaveStatus, comment?: string) => {
if (!selectedLeave || !onStatusUpdate) return;
setIsSubmitting(true);
try {
await onStatusUpdate(selectedLeave.id, status, comment);
// Close dialogs
setShowApproveDialog(false);
setShowRejectDialog(false);
setSelectedLeave(null);
} catch (error) {
console.error("Error in status update:", error);
} finally {
setIsSubmitting(false);
}
};
const paginatedLeaves = filteredLeaves.slice(
(page - 1) * itemsPerPage,
page * itemsPerPage
);
// Helper function to get leave type name if getLeaveTypeName is not provided
const getLeaveTypeDisplay = (leave: ExtendedLeave): string => {
if (getLeaveTypeName && leave.leaveMasterId) {
return getLeaveTypeName(leave.leaveMasterId);
}
return leave.type;
};
return (
<Card>
<CardContent>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
<Typography variant="h6" component="h2">
{title} {filteredLeaves.length > 0 && `(${filteredLeaves.length})`}
</Typography>
{showFilters && (
<IconButton
onClick={() => setDisplayFilters(!displayFilters)}
color={displayFilters ? "primary" : "default"}
>
<FilterListIcon />
</IconButton>
)}
</Box>
{displayFilters && (
<Box sx={{ mb: 2 }}>
<Grid container spacing={2}>
<Grid item xs={12} sm={6} md={2}>
<FormControl fullWidth size="small">
<InputLabel>Status</InputLabel>
<Select
value={statusFilter}
label="Status"
onChange={handleStatusFilterChange}
>
<MenuItem value="All">All</MenuItem>
<MenuItem value="Pending">Pending</MenuItem>
<MenuItem value="Approved">Approved</MenuItem>
<MenuItem value="Rejected">Rejected</MenuItem>
<MenuItem value="Cancelled">Cancelled</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={12} sm={6} md={2}>
<FormControl fullWidth size="small">
<InputLabel>Year</InputLabel>
<Select
value={yearFilter.toString()}
label="Year"
onChange={handleYearFilterChange}
>
{years.map(year => (
<MenuItem key={year} value={year}>{year}</MenuItem>
))}
</Select>
</FormControl>
</Grid>
{leaveTypes.length > 0 && (
<Grid item xs={12} sm={6} md={2}>
<FormControl fullWidth size="small">
<InputLabel>Type</InputLabel>
<Select
value={typeFilter}
label="Type"
onChange={handleTypeFilterChange}
>
<MenuItem value="All">All</MenuItem>
{leaveTypes.map(type => (
<MenuItem key={type} value={type}>{type}</MenuItem>
))}
</Select>
</FormControl>
</Grid>
)}
{employees.length > 0 && (
<Grid item xs={12} sm={6} md={2}>
<FormControl fullWidth size="small">
<InputLabel>Employee</InputLabel>
<Select
value={employeeFilter}
label="Employee"
onChange={handleEmployeeFilterChange}
>
<MenuItem value="All">All</MenuItem>
{employees.map(emp => (
<MenuItem key={emp.id} value={emp.id}>
{`${emp.firstName} ${emp.lastName}`.trim()}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
)}
<Grid item xs={12} sm={6} md={4}>
<TextField
fullWidth
size="small"
label="Search"
variant="outlined"
value={searchTerm}
onChange={handleSearchChange}
placeholder="Search by reason or employee name"
/>
</Grid>
</Grid>
</Box>
)}
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
{isLoading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}>
<CircularProgress />
</Box>
) : filteredLeaves.length === 0 ? (
<Box sx={{ p: 4, textAlign: 'center' }}>
<Typography variant="body1" color="textSecondary">
No leave requests found
</Typography>
</Box>
) : (
<>
<Grid container spacing={2}>
{paginatedLeaves.map(leave => (
<Grid item xs={12} sm={6} md={4} key={leave.id}>
<LeaveCard
leave={leave}
employeeName={leave.employeeName || 'Unknown'}
leaveType={getLeaveTypeDisplay(leave)}
onView={() => handleViewLeave(leave)}
onApprove={canApprove && leave.status === 'Pending' ? () => handleApproveLeave(leave) : undefined}
onReject={canApprove && leave.status === 'Pending' ? () => handleRejectLeave(leave) : undefined}
/>
</Grid>
))}
</Grid>
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 3 }}>
<Pagination
count={Math.ceil(filteredLeaves.length / itemsPerPage)}
page={page}
onChange={handlePageChange}
color="primary"
/>
</Box>
</>
)}
</CardContent>
{selectedLeave && (
<>
<LeaveDetail
open={showDetailDialog}
leave={selectedLeave}
onClose={() => setShowDetailDialog(false)}
/>
{canApprove && (
<>
<LeaveStatusUpdate
open={showApproveDialog}
leave={selectedLeave}
onClose={() => setShowApproveDialog(false)}
onSubmit={(comment) => handleSubmitStatusUpdate("Approved", comment)}
isSubmitting={isSubmitting}
title="Approve Leave Request"
actionLabel="Approve"
/>
<LeaveStatusUpdate
open={showRejectDialog}
leave={selectedLeave}
onClose={() => setShowRejectDialog(false)}
onSubmit={(comment) => handleSubmitStatusUpdate("Rejected", comment)}
isSubmitting={isSubmitting}
title="Reject Leave Request"
actionLabel="Reject"
/>
</>
)}
</>
)}
</Card>
);
};
export default LeaveList;