import React, { useState, useEffect } from "react"; import { Todo } from "@/types"; import { Card, CardContent, CardHeader } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Pencil, Trash2, CheckCircle2, User, Briefcase, CalendarClock, Clock, ChevronRight, CircleCheck, Circle, CheckCircle } from "lucide-react"; import { format, isAfter, isPast, formatDistance } from "date-fns"; import { employeeService, projectService } from "@/lib/api"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { Progress } from "@/components/ui/progress"; import { cn } from "@/lib/utils"; type TodoItemProps = { todo: Todo; onEdit?: (todo: Todo) => void; onDelete?: (id: number) => void; onStatusChange: (id: number, status: Todo['status']) => void; }; const getPriorityColor = (priority: Todo['priority']) => { switch (priority) { case 'Low': return { bg: 'bg-white', border: 'border-l-4 border-emerald-400', text: 'text-emerald-700', badge: 'bg-emerald-50 text-emerald-700 border border-emerald-200' }; case 'Medium': return { bg: 'bg-white', border: 'border-l-4 border-amber-400', text: 'text-amber-700', badge: 'bg-amber-50 text-amber-700 border border-amber-200' }; case 'High': return { bg: 'bg-white', border: 'border-l-4 border-rose-500', text: 'text-rose-700', badge: 'bg-rose-50 text-rose-700 border border-rose-200' }; default: return { bg: 'bg-white', border: 'border-l-4 border-slate-400', text: 'text-slate-700', badge: 'bg-slate-50 text-slate-700 border border-slate-200' }; } }; const getStatusColor = (status: Todo['status']) => { switch (status) { case 'Pending': return { bg: 'bg-slate-50', text: 'text-slate-700', badge: 'bg-white text-slate-700 border border-slate-200', icon: }; case 'In Progress': return { bg: 'bg-blue-50', text: 'text-blue-700', badge: 'bg-white text-blue-700 border border-blue-200', icon: }; case 'Completed': return { bg: 'bg-green-50', text: 'text-green-700', badge: 'bg-white text-green-700 border border-green-200', icon: }; default: return { bg: 'bg-white', text: 'text-slate-700', badge: 'bg-white text-slate-700 border border-slate-200', icon: null }; } }; const getStatusProgress = (status: Todo['status']): number => { switch (status) { case 'Pending': return 0; case 'In Progress': return 50; case 'Completed': return 100; default: return 0; } }; export function TodoItem({ todo, onEdit, onDelete, onStatusChange }: TodoItemProps) { const [assignee, setAssignee] = useState<{ name: string; email: string } | null>(null); const [project, setProject] = useState<{ name: string } | null>(null); const [isLoadingAssignee, setIsLoadingAssignee] = useState(false); const [isLoadingProject, setIsLoadingProject] = useState(false); const [isExpanded, setIsExpanded] = useState(false); const [isHovered, setIsHovered] = useState(false); const priorityStyles = getPriorityColor(todo.priority); const statusStyles = getStatusColor(todo.status); const dueDate = new Date(todo.dueDate); const isPastDue = isPast(dueDate) && todo.status !== 'Completed'; const statusProgress = getStatusProgress(todo.status); const isCompleted = todo.status === 'Completed'; useEffect(() => { const fetchAssignee = async () => { if (!todo.assigneeId) return; setIsLoadingAssignee(true); try { const response = await employeeService.getById(todo.assigneeId); if (response && response.id) { setAssignee({ name: `${response.firstName} ${response.lastName}`, email: response.email }); } } catch (error) { console.error("Failed to fetch assignee details", error); } finally { setIsLoadingAssignee(false); } }; fetchAssignee(); }, [todo.assigneeId]); useEffect(() => { const fetchProject = async () => { if (!todo.projectId) return; setIsLoadingProject(true); try { const response = await projectService.getById(todo.projectId); if (response && response.id) { setProject({ name: response.projectName }); } } catch (error) { console.error("Failed to fetch project details", error); } finally { setIsLoadingProject(false); } }; fetchProject(); }, [todo.projectId]); const handleStatusChange = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); let newStatus: Todo['status'] = 'Pending'; switch (todo.status) { case 'Pending': newStatus = 'In Progress'; break; case 'In Progress': newStatus = 'Completed'; break; case 'Completed': newStatus = 'Pending'; break; } onStatusChange(todo.id, newStatus); }; const toggleComplete = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); const newStatus = isCompleted ? 'Pending' : 'Completed'; onStatusChange(todo.id, newStatus); }; const getInitials = (name: string) => { return name.split(' ') .map(part => part.charAt(0)) .join('') .toUpperCase(); }; const toggleExpand = () => { setIsExpanded(!isExpanded); }; return (
{/* Mobile-first layout */}
{/* Checkbox */}
setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} >
{isCompleted ? ( ) : ( )}
{/* Main content */}
{/* Title and Priority - Mobile responsive */}

{todo.title}

{todo.priority}
{/* Description */}

{todo.description}

{/* Status and Date - Mobile responsive */}
{statusStyles.icon} {todo.status} {todo.status === 'In Progress' ? 'In Prog.' : todo.status}

Status: {todo.status}

{format(dueDate, 'MMM dd, yyyy')} {format(dueDate, 'MMM dd')} {isPastDue && ( (Overdue) ! )}
{/* Progress bar */}
{isExpanded && ( {/* Full description */}
{todo.description}
{/* Details grid - Mobile responsive */}
Due: {format(dueDate, 'PPP')}
{todo.assigneeId && (
Assigned to: {isLoadingAssignee ? ( Loading... ) : assignee ? (
{getInitials(assignee.name)} {assignee.name}
) : ( Employee {todo.assigneeId} )}
)} {todo.projectId && (
Project: {isLoadingProject ? ( Loading... ) : project ? ( {project.name} ) : ( Project {todo.projectId} )}
)}
Created: {format(new Date(todo.createdAt || Date.now()), 'MMM dd, yyyy')}
{/* Action buttons - Mobile responsive */}
{onEdit && ( )} {onDelete && ( )}
)}
); }