'use client'; import { useState } from 'react'; import Link from 'next/link'; import type { Route } from 'next'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { ArrowLeft, Bell, Download, FileSignature, Mail, StopCircle } from 'lucide-react'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { PageHeader } from '@/components/shared/page-header'; import { StatusPill, type StatusPillStatus } from '@/components/ui/status-pill'; import { EmptyState } from '@/components/ui/empty-state'; import { useRealtimeInvalidation } from '@/hooks/use-realtime-invalidation'; import { apiFetch } from '@/lib/api/client'; import { ClientLink, YachtLink, BerthLink } from '@/components/reservations/reservation-list'; interface ReservationDoc { id: string; title: string; status: string; documentType: string; signedFileId: string | null; signers: Array<{ id: string; status: string; signerName: string }>; } interface ReservationData { id: string; status: string; startDate: string; endDate: string | null; tenureType: string; contractFileId: string | null; berthId: string; yachtId: string; clientId: string; notes: string | null; } const RESERVATION_PILL: Record = { pending: 'pending', active: 'active', ended: 'archived', cancelled: 'cancelled', }; function todayIso(): string { return new Date().toISOString().slice(0, 10); } interface EndReservationDialogProps { reservationId: string; open: boolean; onOpenChange: (open: boolean) => void; } function EndReservationDialog({ reservationId, open, onOpenChange }: EndReservationDialogProps) { const qc = useQueryClient(); const [endDate, setEndDate] = useState(todayIso); const [submitting, setSubmitting] = useState(false); async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setSubmitting(true); try { await apiFetch(`/api/v1/berth-reservations/${reservationId}`, { method: 'PATCH', body: { action: 'end', endDate }, }); qc.invalidateQueries({ queryKey: ['reservation', reservationId] }); toast.success('Reservation ended'); onOpenChange(false); } catch (err) { toast.error(err instanceof Error ? err.message : 'Failed to end reservation'); } finally { setSubmitting(false); } } return ( End reservation
setEndDate(e.target.value)} required />
); } interface ReservationDetailProps { reservationId: string; portSlug: string; } export function ReservationDetail({ reservationId, portSlug }: ReservationDetailProps) { const [endDialogOpen, setEndDialogOpen] = useState(false); const reservation = useQuery<{ data: ReservationData }>({ queryKey: ['reservation', reservationId], queryFn: () => apiFetch(`/api/v1/berth-reservations/${reservationId}`), }); const documentsForRes = useQuery<{ data: ReservationDoc[] }>({ queryKey: ['documents', 'by-reservation', reservationId], queryFn: () => apiFetch( `/api/v1/documents?documentType=reservation_agreement&signatureOnly=true&limit=10`, ).then((res) => { const r = res as { data: ReservationDoc[] & Array<{ reservationId?: string }> }; return { data: r.data.filter( (d: ReservationDoc & { reservationId?: string }) => d.reservationId === reservationId, ), } as { data: ReservationDoc[] }; }), }); useRealtimeInvalidation({ 'document:created': [['documents', 'by-reservation', reservationId]], 'document:completed': [ ['documents', 'by-reservation', reservationId], ['reservation', reservationId], ], 'document:cancelled': [['documents', 'by-reservation', reservationId]], }); if (reservation.isLoading) { return
; } if (reservation.error || !reservation.data) { return ( Back } /> ); } const res = reservation.data.data; const docs = documentsForRes.data?.data ?? []; const activeAgreement = docs.find((d) => ['sent', 'partially_signed'].includes(d.status)); const completedAgreement = docs.find((d) => ['completed', 'signed'].includes(d.status)); const renderAgreementCard = (): React.ReactNode => { if (completedAgreement) { return (

Agreement signed

{completedAgreement.title}

Completed
{completedAgreement.signedFileId ? ( ) : null}

Signed contract attached to this reservation.

); } if (activeAgreement) { const signedCount = activeAgreement.signers.filter((s) => s.status === 'signed').length; return (

Agreement out for signing

{signedCount}/{activeAgreement.signers.length} signed · {activeAgreement.title}

{activeAgreement.status.replace(/_/g, ' ')}
); } return ( } title="No reservation agreement yet" body="Generate an agreement for the parties to sign before activation." actions={ } /> ); }; return (
{res.status} {res.contractFileId ? Contract attached : No contract} } actions={
{res.status === 'active' && ( )}
} variant="gradient" />

Reservation details

Berth
Yacht
Client
Tenure
{res.tenureType.replace(/_/g, ' ')}
{res.notes ? (
Notes

{res.notes}

) : null}

Agreement

{renderAgreementCard()}
); }