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 (
);
}