'use client'; import { useState } from 'react'; import type { ReactNode } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Textarea } from '@/components/ui/textarea'; import { FormErrorSummary } from '@/components/forms/form-error-summary'; import { useFormScrollToError } from '@/hooks/use-form-scroll-to-error'; import { PermissionGate } from '@/components/shared/permission-gate'; import { apiFetch } from '@/lib/api/client'; import { toastError } from '@/lib/api/toast-error'; import { useVocabulary } from '@/hooks/use-vocabulary'; import { updateBerthStatusSchema, type UpdateBerthStatusInput } from '@/lib/validators/berths'; import { BERTH_STATUSES, stageLabel } from '@/lib/constants'; import { cn } from '@/lib/utils'; const STATUS_LABELS: Record = { available: 'Available', under_offer: 'Under Offer', sold: 'Sold', }; interface InterestOption { id: string; clientName: string; pipelineStage: string; } /** * Click-to-change berth status from the berths LIST. Wraps the status chip * (passed as children) in a button that opens a compact change-status dialog * — status dropdown + required reason (with quick-pick chips) + an optional * interest link when moving to under_offer/sold. Same PATCH endpoint + * validator + audit as the berth detail page. Reps without `berths.edit` see * a plain, non-interactive chip via the PermissionGate fallback. */ export function BerthStatusQuickEdit({ berthId, currentStatus, children, className, }: { berthId: string; currentStatus: string; children: ReactNode; className?: string; }) { const [open, setOpen] = useState(false); return ( {children}}> {open && ( )} ); } function BerthStatusQuickEditDialog({ berthId, currentStatus, open, onOpenChange, }: { berthId: string; currentStatus: string; open: boolean; onOpenChange: (open: boolean) => void; }) { const queryClient = useQueryClient(); const reasonChips = useVocabulary('berth_status_change_reasons'); const { register, handleSubmit, setValue, watch, reset, formState: { errors, isSubmitting }, } = useForm({ resolver: zodResolver(updateBerthStatusSchema), defaultValues: { status: currentStatus as (typeof BERTH_STATUSES)[number], reason: '' }, }); const submitWithScroll = useFormScrollToError(handleSubmit, errors); const status = watch('status'); const interestId = watch('interestId'); const showInterestPicker = status === 'under_offer' || status === 'sold'; // Active interests for the picker — only fetched once the picker is shown. const interestsQuery = useQuery<{ data: InterestOption[] }>({ queryKey: ['interests', 'status-link-picker'], queryFn: () => apiFetch('/api/v1/interests?pageSize=200&sort=updatedAt&order=desc'), enabled: open && showInterestPicker, staleTime: 60_000, }); const interestOptions = interestsQuery.data?.data ?? []; async function onSubmit(data: UpdateBerthStatusInput) { try { await apiFetch(`/api/v1/berths/${berthId}/status`, { method: 'PATCH', body: data }); queryClient.invalidateQueries({ queryKey: ['berths'] }); queryClient.invalidateQueries({ queryKey: ['berth', berthId] }); queryClient.invalidateQueries({ queryKey: ['interests'] }); toast.success('Status updated'); reset(); onOpenChange(false); } catch (err) { toastError(err); } } return ( Change status
{reasonChips.length > 0 && (
{reasonChips.map((chip) => ( ))}
)}