import { useEffect, useState, memo } from "react"; import { useForm, Controller } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import * as Dialog from "@radix-ui/react-dialog"; import * as Select from "@radix-ui/react-select"; import { cn } from "@/lib/cn"; import { formatDateForApi } from "@/utils/dates"; import { useRooms, useInstructors, useStudents } from "@/api/hooks"; import type { Class } from "@/types"; import { Button, SpinnerIcon, XIcon, ChevronDownIcon, CheckIcon, TrashIcon, } from "@/components/ui"; const formSchema = z .object({ course_name: z.string().min(1, "Course name is required"), chapter_topic: z.string().min(1, "Chapter/topic is required"), date: z.string().min(1, "Date is required"), start_time: z.string().min(1, "Start time is required"), end_time: z.string().min(1, "End time is required"), room_id: z .number({ required_error: "Room is required" }) .positive("Room is required"), instructor_id: z .number({ required_error: "Instructor is required" }) .positive("Instructor is required"), student_ids: z.array(z.number()), }) .refine((data) => data.start_time < data.end_time, { message: "End time must be after start time", path: ["end_time"], }); type FormData = z.infer; interface ClassFormDialogProps { open: boolean; onClose: () => void; onSubmit: (data: FormData) => Promise; onDelete?: () => Promise; editingClass?: Class | null; selectedDate: Date; isLoading?: boolean; } interface FormFieldProps { label: string; required?: boolean; error?: string; children: React.ReactNode; } function FormField({ label, required, error, children }: FormFieldProps) { return (
{children} {error &&

{error}

}
); } export const ClassFormDialog = memo(function ClassFormDialog({ open, onClose, onSubmit, onDelete, editingClass, selectedDate, isLoading = false, }: ClassFormDialogProps) { const { data: rooms = [], isLoading: roomsLoading } = useRooms(); const { data: instructors = [], isLoading: instructorsLoading } = useInstructors(); const { data: students = [], isLoading: studentsLoading } = useStudents(); const [selectedStudents, setSelectedStudents] = useState([]); const { register, handleSubmit, control, reset, setValue, formState: { errors }, } = useForm({ resolver: zodResolver(formSchema), defaultValues: { course_name: "", chapter_topic: "", date: formatDateForApi(selectedDate), start_time: "09:00", end_time: "10:30", room_id: 0, instructor_id: 0, student_ids: [], }, }); useEffect(() => { if (open) { if (editingClass) { reset({ course_name: editingClass.course_name, chapter_topic: editingClass.chapter_topic, date: editingClass.date, start_time: editingClass.start_time.slice(0, 5), end_time: editingClass.end_time.slice(0, 5), room_id: editingClass.room_id, instructor_id: editingClass.instructor_id, student_ids: editingClass.students.map((s) => s.id), }); setSelectedStudents(editingClass.students.map((s) => s.id)); } else { reset({ course_name: "", chapter_topic: "", date: formatDateForApi(selectedDate), start_time: "09:00", end_time: "10:30", room_id: 0, instructor_id: 0, student_ids: [], }); setSelectedStudents([]); } } }, [open, editingClass, selectedDate, reset]); useEffect(() => { setValue("student_ids", selectedStudents); }, [selectedStudents, setValue]); const handleFormSubmit = async (data: FormData) => { await onSubmit(data); }; const toggleStudent = (studentId: number) => { setSelectedStudents((prev) => prev.includes(studentId) ? prev.filter((id) => id !== studentId) : [...prev, studentId] ); }; const isEditing = !!editingClass; const isDataLoading = roomsLoading || instructorsLoading || studentsLoading; const inputStyles = cn( "w-full px-4 py-2.5 rounded-xl border", "focus:ring-2 focus:ring-primary-500 focus:border-primary-500", "transition-all duration-200" ); const inputErrorStyles = "border-error-500 bg-error-50"; const inputNormalStyles = "border-slate-200 bg-white"; return ( !open && onClose()}>
{isEditing ? "Edit Class" : "Create New Class"} {isEditing ? "Update class information" : "Fill in the details below"}
{isDataLoading && (
Loading form data...
)} {!isDataLoading && (
( field.onChange(parseInt(value, 10)) } > {rooms.map((room) => ( {room.name} Capacity: {room.capacity} ))} )} /> ( field.onChange(parseInt(value, 10)) } > {instructors.map((instructor) => ( {instructor.name} ))} )} /> ( { const studentId = parseInt(value, 10); toggleStudent(studentId); }} > {students.map((student) => { const isSelected = selectedStudents.includes( student.id ); return ( {student.name} {isSelected && ( )} ); })} )} /> {selectedStudents.length > 0 && (
{selectedStudents.map((studentId) => { const student = students.find( (s) => s.id === studentId ); if (!student) return null; return ( ); })}
)}

{selectedStudents.length} student {selectedStudents.length !== 1 ? "s" : ""} enrolled

{isEditing && onDelete && ( )}
)}
); });