"use client"; import { zodResolver } from "@hookform/resolvers/zod"; import { FlaskConical } from "lucide-react"; import { useState, useEffect } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { Input } from "~/components/ui/input"; import { Label } from "~/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "~/components/ui/select"; import { Textarea } from "~/components/ui/textarea"; import { EntityForm, FormField, FormSection, NextSteps, Tips, } from "~/components/ui/entity-form"; import { useBreadcrumbsEffect } from "~/components/ui/breadcrumb-provider"; import { useRouter } from "next/navigation"; import { api } from "~/trpc/react"; const studySchema = z.object({ name: z.string().min(1, "Study name is required").max(255, "Name too long"), description: z .string() .min(10, "Description must be at least 10 characters") .max(1000, "Description too long"), institution: z .string() .min(1, "Institution is required") .max(255, "Institution name too long"), irbProtocolNumber: z.string().max(100, "Protocol number too long").optional(), status: z.enum(["draft", "active", "completed", "archived"]), }); type StudyFormData = z.infer; interface StudyFormProps { mode: "create" | "edit"; studyId?: string; } export function StudyForm({ mode, studyId }: StudyFormProps) { const router = useRouter(); const [isSubmitting, setIsSubmitting] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const [error, setError] = useState(null); const form = useForm({ resolver: zodResolver(studySchema), defaultValues: { status: "draft" as const, }, }); // Fetch study data for edit mode const { data: study, isLoading, error: fetchError, } = api.studies.get.useQuery( { id: studyId! }, { enabled: mode === "edit" && !!studyId }, ); // Set breadcrumbs const breadcrumbs = [ { label: "Dashboard", href: "/dashboard" }, { label: "Studies", href: "/studies" }, ...(mode === "edit" && study ? [{ label: study.name, href: `/studies/${study.id}` }, { label: "Edit" }] : [{ label: "New Study" }]), ]; useBreadcrumbsEffect(breadcrumbs); // Populate form with existing data in edit mode useEffect(() => { if (mode === "edit" && study) { form.reset({ name: study.name, description: study.description ?? "", institution: study.institution ?? "", irbProtocolNumber: study.irbProtocol ?? "", status: study.status, }); } }, [study, mode, form]); const createStudyMutation = api.studies.create.useMutation(); const updateStudyMutation = api.studies.update.useMutation(); const deleteStudyMutation = api.studies.delete.useMutation(); // Form submission const onSubmit = async (data: StudyFormData) => { setIsSubmitting(true); setError(null); try { if (mode === "create") { const newStudy = await createStudyMutation.mutateAsync({ name: data.name, description: data.description, institution: data.institution, irbProtocol: data.irbProtocolNumber ?? undefined, }); router.push(`/studies/${newStudy.id}`); } else { const updatedStudy = await updateStudyMutation.mutateAsync({ id: studyId!, name: data.name, description: data.description, institution: data.institution, irbProtocol: data.irbProtocolNumber ?? undefined, status: data.status, }); router.push(`/studies/${updatedStudy.id}`); } } catch (error) { setError( `Failed to ${mode} study: ${error instanceof Error ? error.message : "Unknown error"}`, ); } finally { setIsSubmitting(false); } }; // Delete handler const onDelete = async () => { if (!studyId) return; setIsDeleting(true); setError(null); try { await deleteStudyMutation.mutateAsync({ id: studyId }); router.push("/studies"); } catch (error) { setError( `Failed to delete study: ${error instanceof Error ? error.message : "Unknown error"}`, ); } finally { setIsDeleting(false); } }; // Loading state for edit mode if (mode === "edit" && isLoading) { return
Loading study...
; } // Error state for edit mode if (mode === "edit" && fetchError) { return
Error loading study: {fetchError.message}
; } // Form fields const formFields = ( {form.formState.errors.name && (

{form.formState.errors.name.message}

)}