From ddcffe9f6fd586d5fed83adceeec755ae99faf90 Mon Sep 17 00:00:00 2001 From: Matt Ciaccio Date: Fri, 24 Apr 2026 14:22:06 +0200 Subject: [PATCH] feat(ui): add reservations tab to berth detail --- .../berths/berth-reservations-tab.tsx | 90 +++++++++++++++++++ src/components/berths/berth-tabs.tsx | 11 ++- 2 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 src/components/berths/berth-reservations-tab.tsx diff --git a/src/components/berths/berth-reservations-tab.tsx b/src/components/berths/berth-reservations-tab.tsx new file mode 100644 index 0000000..a5c4ae8 --- /dev/null +++ b/src/components/berths/berth-reservations-tab.tsx @@ -0,0 +1,90 @@ +'use client'; + +import { useState } from 'react'; +import { useParams } from 'next/navigation'; +import { useQuery } from '@tanstack/react-query'; +import { Plus } from 'lucide-react'; + +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { PermissionGate } from '@/components/shared/permission-gate'; +import { EmptyState } from '@/components/shared/empty-state'; +import { ReservationList, type ReservationRow } from '@/components/reservations/reservation-list'; +import { BerthReserveDialog } from '@/components/reservations/berth-reserve-dialog'; +import { useRealtimeInvalidation } from '@/hooks/use-realtime-invalidation'; +import { apiFetch } from '@/lib/api/client'; + +interface BerthReservationsTabProps { + berthId: string; +} + +export function BerthReservationsTab({ berthId }: BerthReservationsTabProps) { + const routeParams = useParams<{ portSlug: string }>(); + const portSlug = routeParams?.portSlug ?? ''; + const [reserveOpen, setReserveOpen] = useState(false); + + const { data, isLoading } = useQuery<{ data: ReservationRow[]; pagination?: unknown }>({ + queryKey: ['berths', berthId, 'reservations'], + queryFn: () => + apiFetch( + `/api/v1/berths/${berthId}/reservations?page=1&limit=50&order=desc&includeArchived=false`, + ), + }); + + useRealtimeInvalidation({ + 'berth_reservation:created': [['berths', berthId, 'reservations']], + 'berth_reservation:activated': [['berths', berthId, 'reservations']], + 'berth_reservation:ended': [['berths', berthId, 'reservations']], + 'berth_reservation:cancelled': [['berths', berthId, 'reservations']], + }); + + const reservations = data?.data ?? []; + const active = reservations.find((r) => r.status === 'active'); + const history = reservations.filter((r) => r.status !== 'active'); + + return ( +
+
+

Reservations

+ + + +
+ + {/* Active reservation card */} + + + Active reservation + + + {active ? ( + + ) : ( +

No active reservation.

+ )} +
+
+ + {/* History */} + + + History + + + {isLoading ? ( +

Loading…

+ ) : history.length === 0 ? ( + + ) : ( + + )} +
+
+ + +
+ ); +} diff --git a/src/components/berths/berth-tabs.tsx b/src/components/berths/berth-tabs.tsx index caa6d13..cbe71f4 100644 --- a/src/components/berths/berth-tabs.tsx +++ b/src/components/berths/berth-tabs.tsx @@ -3,6 +3,7 @@ import { type DetailTab } from '@/components/shared/detail-layout'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { TagBadge } from '@/components/shared/tag-badge'; +import { BerthReservationsTab } from './berth-reservations-tab'; type BerthData = { id: string; @@ -87,7 +88,10 @@ function OverviewTab({ berth }: { berth: BerthData }) { } /> - + , }, + { + id: 'reservations', + label: 'Reservations', + content: , + }, { id: 'waiting-list', label: 'Waiting List',