"use client"; import { zodResolver } from "@hookform/resolvers/zod"; import { TestTube } from "lucide-react"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { useBreadcrumbsEffect } from "~/components/ui/breadcrumb-provider"; import { EntityForm, FormField, FormSection, NextSteps, Tips, } from "~/components/ui/entity-form"; 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 { useStudyContext } from "~/lib/study-context"; import { api } from "~/trpc/react"; const trialSchema = z.object({ experimentId: z.string().uuid("Please select an experiment"), participantId: z.string().uuid("Please select a participant"), scheduledAt: z.string().min(1, "Please select a date and time"), wizardId: z.string().uuid().optional(), notes: z.string().max(1000, "Notes cannot exceed 1000 characters").optional(), sessionNumber: z .number() .min(1, "Session number must be at least 1") .optional(), }); type TrialFormData = z.infer; interface TrialFormProps { mode: "create" | "edit"; trialId?: string; studyId?: string; } export function TrialForm({ mode, trialId, studyId }: TrialFormProps) { const router = useRouter(); const { selectedStudyId } = useStudyContext(); const contextStudyId = studyId ?? selectedStudyId; const [isSubmitting, setIsSubmitting] = useState(false); const [isDeleting] = useState(false); const [error, setError] = useState(null); const form = useForm({ resolver: zodResolver(trialSchema), defaultValues: { sessionNumber: 1, }, }); // Fetch trial data for edit mode const { data: trial, isLoading, error: fetchError, } = api.trials.get.useQuery( { id: trialId! }, { enabled: mode === "edit" && !!trialId }, ); // Fetch experiments for the selected study const { data: experimentsData, isLoading: experimentsLoading } = api.experiments.list.useQuery( { studyId: contextStudyId! }, { enabled: !!contextStudyId }, ); // Fetch participants for the selected study const { data: participantsData, isLoading: participantsLoading } = api.participants.list.useQuery( { studyId: contextStudyId!, limit: 100 }, { enabled: !!contextStudyId }, ); // Fetch users who can be wizards const { data: usersData, isLoading: usersLoading } = api.users.getWizards.useQuery(); // Set breadcrumbs const breadcrumbs = [ { label: "Dashboard", href: "/dashboard" }, { label: "Studies", href: "/studies" }, ...(contextStudyId ? [ { label: "Study", href: `/studies/${contextStudyId}`, }, { label: "Trials", href: `/studies/${contextStudyId}/trials` }, ...(mode === "edit" && trial ? [ { label: `Trial ${trial.sessionNumber || trial.id.slice(-8)}`, href: `/trials/${trial.id}`, }, { label: "Edit" }, ] : [{ label: "New Trial" }]), ] : [ { label: "Trials", href: "/trials" }, ...(mode === "edit" && trial ? [ { label: `Trial ${trial.sessionNumber || trial.id.slice(-8)}`, href: `/trials/${trial.id}`, }, { label: "Edit" }, ] : [{ label: "New Trial" }]), ]), ]; useBreadcrumbsEffect(breadcrumbs); // Populate form with existing data in edit mode useEffect(() => { if (mode === "edit" && trial) { form.reset({ experimentId: trial.experimentId, participantId: trial?.participantId ?? "", scheduledAt: trial.scheduledAt ? new Date(trial.scheduledAt).toISOString().slice(0, 16) : "", wizardId: trial.wizardId ?? undefined, notes: trial.notes ?? "", sessionNumber: trial.sessionNumber ?? 1, }); } }, [trial, mode, form]); const createTrialMutation = api.trials.create.useMutation(); const updateTrialMutation = api.trials.update.useMutation(); // Form submission const onSubmit = async (data: TrialFormData) => { setIsSubmitting(true); setError(null); try { if (mode === "create") { const newTrial = await createTrialMutation.mutateAsync({ experimentId: data.experimentId, participantId: data.participantId, scheduledAt: new Date(data.scheduledAt), wizardId: data.wizardId, sessionNumber: data.sessionNumber ?? 1, notes: data.notes ?? undefined, }); router.push(`/trials/${newTrial!.id}`); } else { const updatedTrial = await updateTrialMutation.mutateAsync({ id: trialId!, scheduledAt: new Date(data.scheduledAt), wizardId: data.wizardId, sessionNumber: data.sessionNumber ?? 1, notes: data.notes ?? undefined, }); router.push(`/trials/${updatedTrial!.id}`); } } catch (error) { setError( `Failed to ${mode} trial: ${error instanceof Error ? error.message : "Unknown error"}`, ); } finally { setIsSubmitting(false); } }; // Delete handler (trials cannot be deleted in this version) const onDelete = undefined; // Loading state for edit mode if (mode === "edit" && isLoading) { return
Loading trial...
; } // Error state for edit mode if (mode === "edit" && fetchError) { return
Error loading trial: {fetchError.message}
; } // Form fields const formFields = ( <> {form.formState.errors.experimentId && (

{form.formState.errors.experimentId.message}

)} {mode === "edit" && (

Experiment cannot be changed after creation

)}
{form.formState.errors.participantId && (

{form.formState.errors.participantId.message}

)} {mode === "edit" && (

Participant cannot be changed after creation

)}
{form.formState.errors.scheduledAt && (

{form.formState.errors.scheduledAt.message}

)}

When should this trial be conducted?

{form.formState.errors.sessionNumber && (

{form.formState.errors.sessionNumber.message}

)}

Session number for this participant (for multi-session studies)

Optional: Assign a specific wizard to operate this trial