import React, { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { Todo, TodoCreateRequest, TodoUpdateRequest } from "@/types"; import { Button } from "@/components/ui/button"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Calendar } from "@/components/ui/calendar"; import { format } from "date-fns"; import { CalendarIcon, Check, ChevronDown, Loader2, Search, User, Briefcase } from "lucide-react"; import { cn } from "@/lib/utils"; import { Badge } from "@/components/ui/badge"; import { employeeService, projectService } from "@/lib/api"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from "@/components/ui/command"; import { useAuth } from "@/lib/auth-context"; // Custom dropdown component interface CustomDropdownProps { value: string; onChange: (value: string) => void; options: { value: string; label: string; color?: string }[]; placeholder: string; showBadge?: boolean; } function CustomDropdown({ value, onChange, options, placeholder, showBadge = false }: CustomDropdownProps) { const [open, setOpen] = useState(false); const getLabel = () => { return options.find(option => option.value === value)?.label || placeholder; }; const getColor = () => { return options.find(option => option.value === value)?.color || ""; }; const handleButtonClick = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); setOpen(!open); }; const handleOptionClick = (optionValue: string) => { onChange(optionValue); setOpen(false); }; return (
{showBadge && value ? (
{getLabel()}
) : ( {getLabel()} )}
{open && (
{options.map((option) => (
handleOptionClick(option.value)} > {showBadge ? ( {option.label} ) : ( option.label )} {value === option.value && }
))}
)}
); } // Custom Employee Dropdown component interface CustomEmployeeDropdownProps { value?: number; onChange: (value: number) => void; employees: Array<{id: number; firstName: string; lastName: string}>; isLoading: boolean; placeholder: string; } function CustomEmployeeDropdown({ value, onChange, employees, isLoading, placeholder }: CustomEmployeeDropdownProps) { const [open, setOpen] = useState(false); const [searchTerm, setSearchTerm] = useState(""); const getEmployeeName = (id?: number) => { if (!id) return placeholder; const employee = employees.find(emp => emp.id === id); return employee ? `${employee.firstName} ${employee.lastName}` : `Employee ${id}`; }; const getInitials = (firstName: string, lastName: string) => { return (firstName.charAt(0) + lastName.charAt(0)).toUpperCase(); }; const handleButtonClick = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); setOpen(!open); }; const handleOptionClick = (employeeId: number) => { onChange(employeeId); setOpen(false); }; // Filter employees based on search term const filteredEmployees = searchTerm ? employees.filter( emp => emp.firstName.toLowerCase().includes(searchTerm.toLowerCase()) || emp.lastName.toLowerCase().includes(searchTerm.toLowerCase()) ) : employees; return (
{value && employees.find(e => e.id === value) ? ( {getInitials( employees.find(e => e.id === value)!.firstName, employees.find(e => e.id === value)!.lastName )} ) : ( )} {getEmployeeName(value)}
{open && (
setSearchTerm(e.target.value)} onClick={(e) => e.stopPropagation()} />
{isLoading ? (
Loading...
) : filteredEmployees.length === 0 ? (
No employee found
) : ( filteredEmployees.map((employee) => (
handleOptionClick(employee.id)} > {getInitials(employee.firstName, employee.lastName)} {employee.firstName} {employee.lastName} {value === employee.id && ( )}
)) )}
)}
); } // Custom Project Dropdown component interface CustomProjectDropdownProps { value?: number; onChange: (value: number) => void; projects: Array<{id: number; projectName: string; projectCode: string}>; isLoading: boolean; placeholder: string; } function CustomProjectDropdown({ value, onChange, projects, isLoading, placeholder }: CustomProjectDropdownProps) { const [open, setOpen] = useState(false); const [searchTerm, setSearchTerm] = useState(""); const getProjectName = (id?: number) => { if (!id) return placeholder; const project = projects.find(proj => proj.id === id); return project ? project.projectName : `Project ${id}`; }; const handleButtonClick = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); setOpen(!open); }; const handleOptionClick = (projectId: number) => { onChange(projectId); setOpen(false); }; // Filter projects based on search term const filteredProjects = searchTerm ? projects.filter( proj => proj.projectName.toLowerCase().includes(searchTerm.toLowerCase()) || proj.projectCode.toLowerCase().includes(searchTerm.toLowerCase()) ) : projects; return (
{!value && } {value && } {getProjectName(value)}
{open && (
setSearchTerm(e.target.value)} onClick={(e) => e.stopPropagation()} />
{isLoading ? (
Loading...
) : filteredProjects.length === 0 ? (
No project found
) : ( filteredProjects.map((project) => (
handleOptionClick(project.id)} > {project.projectName} ({project.projectCode}) {value === project.id && ( )}
)) )}
)}
); } // Custom DatePicker component interface CustomDatePickerProps { value: Date; onChange: (date: Date) => void; } function CustomDatePicker({ value, onChange }: CustomDatePickerProps) { const [open, setOpen] = useState(false); const handleButtonClick = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); setOpen(!open); }; return (
{value ? format(value, "PPP") : Pick a date}
{open && (
{ if (date) { onChange(date); setOpen(false); } }} initialFocus />
)}
); } const todoSchema = z.object({ title: z.string().min(3, { message: "Title must be at least 3 characters" }), description: z.string().min(5, { message: "Description must be at least 5 characters" }), status: z.enum(["Pending", "In Progress", "Completed"], { required_error: "Please select a status", }), priority: z.enum(["Low", "Medium", "High"], { required_error: "Please select a priority", }), dueDate: z.date({ required_error: "Due date is required", }), assigneeId: z.number().optional(), projectId: z.number().optional(), }); type TodoFormValues = z.infer; type TodoFormProps = { todo?: Todo; onSubmit: (data: TodoCreateRequest | TodoUpdateRequest) => void; onCancel: () => void; isLoading?: boolean; }; const statusOptions = [ { value: "Pending", label: "Pending" }, { value: "In Progress", label: "In Progress" }, { value: "Completed", label: "Completed" }, ]; const priorityOptions = [ { value: "Low", label: "Low", color: "bg-green-100 text-green-800" }, { value: "Medium", label: "Medium", color: "bg-yellow-100 text-yellow-800" }, { value: "High", label: "High", color: "bg-red-100 text-red-800" }, ]; type Employee = { id: number; firstName: string; lastName: string; email: string; }; type Project = { id: number; projectName: string; projectCode: string; }; export function TodoForm({ todo, onSubmit, onCancel, isLoading = false }: TodoFormProps) { const { userData } = useAuth(); const [employees, setEmployees] = useState([]); const [projects, setProjects] = useState([]); const [loadingEmployees, setLoadingEmployees] = useState(false); const [loadingProjects, setLoadingProjects] = useState(false); // Initialize form with default values or the provided todo const form = useForm({ resolver: zodResolver(todoSchema), defaultValues: { title: "", description: "", status: "Pending", priority: "Medium", dueDate: new Date(), assigneeId: undefined, projectId: undefined, }, }); // Fetch employees and projects useEffect(() => { const fetchEmployees = async () => { setLoadingEmployees(true); try { const response = await employeeService.getAll(); if (Array.isArray(response)) { setEmployees(response); } } catch (error) { console.error("Failed to fetch employees:", error); } finally { setLoadingEmployees(false); } }; const fetchProjects = async () => { setLoadingProjects(true); try { const response = await projectService.getAll(); if (Array.isArray(response)) { setProjects(response); } } catch (error) { console.error("Failed to fetch projects:", error); } finally { setLoadingProjects(false); } }; fetchEmployees(); fetchProjects(); }, []); // Update form values when todo changes useEffect(() => { if (todo) { form.reset({ title: todo.title, description: todo.description, status: todo.status, priority: todo.priority, dueDate: new Date(todo.dueDate), assigneeId: todo.assigneeId, projectId: todo.projectId, }); } }, [todo, form]); const handleSubmit = (values: TodoFormValues) => { try { const todoData = { ...values, dueDate: format(values.dueDate, "yyyy-MM-dd"), }; // If creating a new todo and no assignee is selected, use the current user's employeeId if (!todo && !values.assigneeId && userData?.employeeId) { todoData.assigneeId = userData.employeeId; } if (todo) { onSubmit({ ...todoData, id: todo.id, } as TodoUpdateRequest); } else { onSubmit(todoData as TodoCreateRequest); } } catch (error) { console.error("Error submitting todo form:", error); // You can add additional error handling here if needed } }; return (
( Title )} /> ( Description