'use client'; import { useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { Loader2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetFooter } from '@/components/ui/sheet'; import { Separator } from '@/components/ui/separator'; import { OwnerPicker, type OwnerRef } from '@/components/shared/owner-picker'; import { TagPicker } from '@/components/shared/tag-picker'; import { apiFetch } from '@/lib/api/client'; import { createYachtSchema, type CreateYachtInput } from '@/lib/validators/yachts'; interface YachtFormProps { open: boolean; onOpenChange: (open: boolean) => void; /** If provided, form is in edit mode */ yacht?: { id: string; name: string; hullNumber?: string | null; registration?: string | null; flag?: string | null; yearBuilt?: number | null; builder?: string | null; model?: string | null; hullMaterial?: string | null; lengthFt?: string | null; widthFt?: string | null; draftFt?: string | null; lengthM?: string | null; widthM?: string | null; draftM?: string | null; currentOwnerType: 'client' | 'company'; currentOwnerId: string; status?: string | null; notes?: string | null; }; } type YachtStatus = 'active' | 'retired' | 'sold_away'; export function YachtForm({ open, onOpenChange, yacht }: YachtFormProps) { const queryClient = useQueryClient(); const isEdit = !!yacht; const [formError, setFormError] = useState(null); const { register, handleSubmit, watch, setValue, reset, formState: { errors, isSubmitting }, } = useForm({ resolver: zodResolver(createYachtSchema), defaultValues: { name: '', status: 'active', tagIds: [], }, }); const tagIds = watch('tagIds') ?? []; const owner = watch('owner') as OwnerRef | undefined; const status = watch('status') ?? 'active'; // Populate form when editing, or reset to defaults in create mode. useEffect(() => { if (yacht && open) { reset({ name: yacht.name, hullNumber: yacht.hullNumber ?? undefined, registration: yacht.registration ?? undefined, flag: yacht.flag ?? undefined, yearBuilt: yacht.yearBuilt ?? undefined, builder: yacht.builder ?? undefined, model: yacht.model ?? undefined, hullMaterial: yacht.hullMaterial ?? undefined, lengthFt: yacht.lengthFt ?? undefined, widthFt: yacht.widthFt ?? undefined, draftFt: yacht.draftFt ?? undefined, lengthM: yacht.lengthM ?? undefined, widthM: yacht.widthM ?? undefined, draftM: yacht.draftM ?? undefined, // Owner is required by the schema in create mode. In edit mode we // strip it before PATCH, but we still satisfy the resolver by // supplying the current owner. owner: { type: yacht.currentOwnerType, id: yacht.currentOwnerId }, status: (yacht.status as YachtStatus | null) ?? 'active', notes: yacht.notes ?? undefined, tagIds: [], }); } else if (!yacht && open) { reset({ name: '', status: 'active', tagIds: [], }); } setFormError(null); }, [yacht, open, reset]); const mutation = useMutation({ mutationFn: async (data: CreateYachtInput) => { if (isEdit) { // updateYachtSchema omits owner + tagIds — strip them from PATCH body. const { owner: _owner, tagIds: _tIds, ...rest } = data; void _owner; void _tIds; await apiFetch(`/api/v1/yachts/${yacht!.id}`, { method: 'PATCH', body: rest, }); } else { await apiFetch('/api/v1/yachts', { method: 'POST', body: data }); } }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['yachts'] }); onOpenChange(false); }, onError: (err: Error) => { setFormError(err.message || 'Failed to save yacht'); }, }); return ( {isEdit ? 'Edit Yacht' : 'New Yacht'}
{ setFormError(null); mutation.mutate(data); })} className="space-y-6 py-6" > {/* Basic */}

Basic

{errors.name &&

{errors.name.message}

}
{errors.yearBuilt && (

{errors.yearBuilt.message}

)}
{/* Build */}

Build

{/* Dimensions (ft) */}

Dimensions (ft)

{/* Dimensions (m) */}

Dimensions (m)

{/* Ownership */}

Ownership

{isEdit ? (

Ownership changes use the Transfer button.

) : (
{ if (v) { setValue('owner', v, { shouldValidate: true }); } }} /> {errors.owner && (

{errors.owner.message ?? 'Owner is required'}

)}
)}
{/* Status */}
{/* Notes */}