Spaces:
Sleeping
Sleeping
| import React from 'react'; | |
| import { Card, CardContent } from '@/components/ui/card'; | |
| import { Badge } from '@/components/ui/badge'; | |
| import { Button } from '@/components/ui/button'; | |
| import { | |
| Calendar, | |
| User, | |
| Clock, | |
| CheckCircle, | |
| ArrowRight, | |
| Edit, | |
| Trash2, | |
| ExternalLink | |
| } from 'lucide-react'; | |
| import { AssetAssignment } from '@/types/asset-management'; | |
| import { format, formatDistanceToNow, isAfter, isBefore } from 'date-fns'; | |
| interface AssignmentTimelineProps { | |
| assignments: AssetAssignment[]; | |
| onEditAssignment?: (assignment: AssetAssignment) => void; | |
| onDeleteAssignment?: (assignment: AssetAssignment) => void; | |
| onNavigateToEmployee?: (employeeId: number) => void; | |
| showActions?: boolean; | |
| } | |
| const AssignmentTimeline: React.FC<AssignmentTimelineProps> = ({ | |
| assignments, | |
| onEditAssignment, | |
| onDeleteAssignment, | |
| onNavigateToEmployee, | |
| showActions = true | |
| }) => { | |
| // Sort assignments by assigned date (most recent first) | |
| const sortedAssignments = [...assignments].sort((a, b) => | |
| new Date(b.assignedDate).getTime() - new Date(a.assignedDate).getTime() | |
| ); | |
| const getAssignmentStatus = (assignment: AssetAssignment) => { | |
| if (!assignment.returnedDate) { | |
| return 'active'; | |
| } | |
| return 'completed'; | |
| }; | |
| const getStatusBadge = (assignment: AssetAssignment) => { | |
| const status = getAssignmentStatus(assignment); | |
| if (status === 'active') { | |
| return ( | |
| <Badge variant="default" className="bg-green-600"> | |
| <Clock className="h-3 w-3 mr-1" /> | |
| Active | |
| </Badge> | |
| ); | |
| } | |
| return ( | |
| <Badge variant="outline"> | |
| <CheckCircle className="h-3 w-3 mr-1" /> | |
| Completed | |
| </Badge> | |
| ); | |
| }; | |
| const getDuration = (assignment: AssetAssignment) => { | |
| const startDate = new Date(assignment.assignedDate); | |
| const endDate = assignment.returnedDate ? new Date(assignment.returnedDate) : new Date(); | |
| if (!assignment.returnedDate) { | |
| return `${formatDistanceToNow(startDate)} (ongoing)`; | |
| } | |
| const durationMs = endDate.getTime() - startDate.getTime(); | |
| const days = Math.floor(durationMs / (1000 * 60 * 60 * 24)); | |
| if (days < 1) { | |
| return 'Less than 1 day'; | |
| } else if (days === 1) { | |
| return '1 day'; | |
| } else if (days < 30) { | |
| return `${days} days`; | |
| } else if (days < 365) { | |
| const months = Math.floor(days / 30); | |
| return months === 1 ? '1 month' : `${months} months`; | |
| } else { | |
| const years = Math.floor(days / 365); | |
| return years === 1 ? '1 year' : `${years} years`; | |
| } | |
| }; | |
| if (assignments.length === 0) { | |
| return ( | |
| <div className="text-center py-8 text-muted-foreground"> | |
| <Calendar className="h-12 w-12 mx-auto mb-4 opacity-50" /> | |
| <p>No assignment history available.</p> | |
| </div> | |
| ); | |
| } | |
| return ( | |
| <div className="space-y-4"> | |
| {/* Timeline */} | |
| <div className="relative"> | |
| {/* Timeline line */} | |
| <div className="absolute left-6 top-0 bottom-0 w-0.5 bg-border"></div> | |
| {sortedAssignments.map((assignment, index) => { | |
| const isActive = getAssignmentStatus(assignment) === 'active'; | |
| const isLast = index === sortedAssignments.length - 1; | |
| return ( | |
| <div key={assignment.assignmentID} className="relative flex items-start space-x-4 pb-6"> | |
| {/* Timeline dot */} | |
| <div className={` | |
| relative z-10 flex items-center justify-center w-12 h-12 rounded-full border-2 | |
| ${isActive | |
| ? 'bg-green-100 border-green-500 text-green-700' | |
| : 'bg-gray-100 border-gray-300 text-gray-500' | |
| } | |
| `}> | |
| {isActive ? ( | |
| <Clock className="h-5 w-5" /> | |
| ) : ( | |
| <CheckCircle className="h-5 w-5" /> | |
| )} | |
| </div> | |
| {/* Timeline content */} | |
| <div className="flex-1 min-w-0"> | |
| <Card className={`${isActive ? 'border-green-200 bg-green-50/50' : ''}`}> | |
| <CardContent className="p-4"> | |
| <div className="flex items-start justify-between"> | |
| <div className="flex-1 min-w-0"> | |
| {/* Header */} | |
| <div className="flex items-center gap-2 mb-2"> | |
| {getStatusBadge(assignment)} | |
| <span className="text-sm text-muted-foreground"> | |
| Assignment #{assignment.assignmentID} | |
| </span> | |
| </div> | |
| {/* Employee info */} | |
| <div className="flex items-center gap-2 mb-2"> | |
| <User className="h-4 w-4 text-muted-foreground" /> | |
| <span className="font-medium"> | |
| {assignment.employeeName || `Employee #${assignment.employeeID}`} | |
| </span> | |
| {onNavigateToEmployee && ( | |
| <Button | |
| variant="ghost" | |
| size="sm" | |
| className="h-6 px-2 text-xs" | |
| onClick={() => onNavigateToEmployee(assignment.employeeID)} | |
| > | |
| <ExternalLink className="h-3 w-3 mr-1" /> | |
| View | |
| </Button> | |
| )} | |
| </div> | |
| {/* Date range */} | |
| <div className="flex items-center gap-2 mb-2 text-sm text-muted-foreground"> | |
| <Calendar className="h-4 w-4" /> | |
| <span>{format(new Date(assignment.assignedDate), 'PPP')}</span> | |
| {assignment.returnedDate && ( | |
| <> | |
| <ArrowRight className="h-3 w-3" /> | |
| <span>{format(new Date(assignment.returnedDate), 'PPP')}</span> | |
| </> | |
| )} | |
| </div> | |
| {/* Duration */} | |
| <div className="text-sm text-muted-foreground mb-2"> | |
| Duration: {getDuration(assignment)} | |
| </div> | |
| {/* Remarks */} | |
| {assignment.remarks && ( | |
| <div className="mt-2"> | |
| <p className="text-sm text-muted-foreground font-medium">Remarks:</p> | |
| <p className="text-sm mt-1">{assignment.remarks}</p> | |
| </div> | |
| )} | |
| </div> | |
| {/* Actions */} | |
| {showActions && ( | |
| <div className="flex items-center gap-1 ml-4"> | |
| {onEditAssignment && ( | |
| <Button | |
| variant="ghost" | |
| size="sm" | |
| onClick={() => onEditAssignment(assignment)} | |
| > | |
| <Edit className="h-4 w-4" /> | |
| </Button> | |
| )} | |
| {onDeleteAssignment && ( | |
| <Button | |
| variant="ghost" | |
| size="sm" | |
| onClick={() => onDeleteAssignment(assignment)} | |
| > | |
| <Trash2 className="h-4 w-4" /> | |
| </Button> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| </CardContent> | |
| </Card> | |
| </div> | |
| </div> | |
| ); | |
| })} | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default AssignmentTimeline; |