'use client'; import { useState } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; 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 { Switch } from '@/components/ui/switch'; import { TagPicker } from '@/components/shared/tag-picker'; import { apiFetch } from '@/lib/api/client'; import { toastError } from '@/lib/api/toast-error'; import { updateBerthSchema, type UpdateBerthInput } from '@/lib/validators/berths'; import { BERTH_AREAS, BERTH_SIDE_PONTOON_OPTIONS, BERTH_MOORING_TYPES, BERTH_CLEAT_TYPES, BERTH_CLEAT_CAPACITIES, BERTH_BOLLARD_TYPES, BERTH_BOLLARD_CAPACITIES, BERTH_ACCESS_OPTIONS, } from '@/lib/constants'; interface BerthFormProps { berth: { id: string; mooringNumber: string; area: string | null; status: string; lengthFt: string | null; lengthM: string | null; widthFt: string | null; widthM: string | null; draftFt: string | null; draftM: string | null; widthIsMinimum: boolean | null; nominalBoatSize: string | null; nominalBoatSizeM: string | null; waterDepth: string | null; waterDepthM: string | null; waterDepthIsMinimum: boolean | null; sidePontoon: string | null; powerCapacity: string | null; voltage: string | null; mooringType: string | null; cleatType: string | null; cleatCapacity: string | null; bollardType: string | null; bollardCapacity: string | null; access: string | null; bowFacing: string | null; price: string | null; priceCurrency: string; tenureType: string; tenureYears: number | null; tenureStartDate: string | null; tenureEndDate: string | null; berthApproved: boolean | null; tags: Array<{ id: string; name: string; color: string }>; }; open: boolean; onOpenChange: (open: boolean) => void; } /** Optional select that allows clearing back to "no value". */ function SelectOrEmpty({ value, onChange, options, placeholder = 'Select…', }: { value: string | undefined; onChange: (next: string | undefined) => void; options: readonly string[]; placeholder?: string; }) { const NONE = '__none'; return ( ); } export function BerthForm({ berth, open, onOpenChange }: BerthFormProps) { const queryClient = useQueryClient(); const [tagIds, setTagIds] = useState(berth.tags.map((t) => t.id)); const numOrUndef = (v: string | null) => (v != null && v !== '' ? Number(v) : undefined); const { register, handleSubmit, setValue, watch, formState: { isSubmitting }, } = useForm({ resolver: zodResolver(updateBerthSchema), defaultValues: { area: berth.area ?? undefined, lengthFt: numOrUndef(berth.lengthFt), lengthM: numOrUndef(berth.lengthM), widthFt: numOrUndef(berth.widthFt), widthM: numOrUndef(berth.widthM), draftFt: numOrUndef(berth.draftFt), draftM: numOrUndef(berth.draftM), widthIsMinimum: berth.widthIsMinimum ?? false, nominalBoatSize: numOrUndef(berth.nominalBoatSize), nominalBoatSizeM: numOrUndef(berth.nominalBoatSizeM), waterDepth: numOrUndef(berth.waterDepth), waterDepthM: numOrUndef(berth.waterDepthM), waterDepthIsMinimum: berth.waterDepthIsMinimum ?? false, sidePontoon: berth.sidePontoon ?? undefined, powerCapacity: numOrUndef(berth.powerCapacity), voltage: numOrUndef(berth.voltage), mooringType: berth.mooringType ?? undefined, cleatType: berth.cleatType ?? undefined, cleatCapacity: berth.cleatCapacity ?? undefined, bollardType: berth.bollardType ?? undefined, bollardCapacity: berth.bollardCapacity ?? undefined, access: berth.access ?? undefined, bowFacing: berth.bowFacing ?? undefined, price: numOrUndef(berth.price), priceCurrency: berth.priceCurrency, tenureType: berth.tenureType as 'permanent' | 'fixed_term', tenureYears: berth.tenureYears ?? undefined, tenureStartDate: berth.tenureStartDate ?? undefined, tenureEndDate: berth.tenureEndDate ?? undefined, berthApproved: berth.berthApproved ?? false, }, }); const tagMutation = useMutation({ mutationFn: (ids: string[]) => apiFetch(`/api/v1/berths/${berth.id}/tags`, { method: 'PUT', body: { tagIds: ids }, }), }); async function onSubmit(data: UpdateBerthInput) { try { await apiFetch(`/api/v1/berths/${berth.id}`, { method: 'PATCH', body: data, }); await tagMutation.mutateAsync(tagIds); queryClient.invalidateQueries({ queryKey: ['berths'] }); queryClient.invalidateQueries({ queryKey: ['berth', berth.id] }); toast.success('Berth updated'); onOpenChange(false); } catch (err: unknown) { toastError(err); } } const tenureType = watch('tenureType'); const area = watch('area'); const sidePontoon = watch('sidePontoon'); const mooringType = watch('mooringType'); const cleatType = watch('cleatType'); const cleatCapacity = watch('cleatCapacity'); const bollardType = watch('bollardType'); const bollardCapacity = watch('bollardCapacity'); const access = watch('access'); return ( Edit Berth {berth.mooringNumber}
{/* Basic Info */}

Basic Info

setValue('area', v)} options={BERTH_AREAS} />
setValue('berthApproved', v)} />
{/* Dimensions */}

Dimensions

setValue('widthIsMinimum', v)} />
setValue('waterDepthIsMinimum', v)} />
{/* Mooring & Hardware */}

Mooring & Hardware

setValue('sidePontoon', v)} options={BERTH_SIDE_PONTOON_OPTIONS} />
setValue('mooringType', v)} options={BERTH_MOORING_TYPES} />
setValue('cleatType', v)} options={BERTH_CLEAT_TYPES} />
setValue('cleatCapacity', v)} options={BERTH_CLEAT_CAPACITIES} />
setValue('bollardType', v)} options={BERTH_BOLLARD_TYPES} />
setValue('bollardCapacity', v)} options={BERTH_BOLLARD_CAPACITIES} />
{/* Power */}

Power

{/* Access */}

Access

setValue('access', v)} options={BERTH_ACCESS_OPTIONS} />
{/* Price */}

Price

{/* Tenure */}

Tenure

{tenureType === 'fixed_term' && (
)}
{/* Tags */}

Tags

); }