feat(marina): end-reservation UI + global list, yacht tabs, dashboard distinct count
- End-reservation: API handler existed but had no UI surface. Adds an
"End reservation" button + date dialog on the reservation detail page,
visible only when status is `active`.
- New port-scoped `GET /api/v1/berth-reservations` list endpoint and
`[portSlug]/berth-reservations` page so users can see all reservations
across all berths from one place (was 404).
- Berths "Edit" menu pushed `/berths/{id}?edit=true` but the detail page
never read the param — it now auto-opens the edit sheet on mount and
strips `edit` from the URL.
- Reservation detail no longer shows raw 8-char UUIDs for Berth / Yacht
/ Client; reuses the lazy-fetching link components from the list view.
- Yacht "Interests" and "Reservations" tabs replaced their "Coming soon"
stubs with real lists fetched from the existing service routes.
- Dashboard "Pipeline Value" KPI used `select(berthId, price)` and
summed per active interest, so a berth with three open interests was
counted three times. Switched to `selectDistinct(berthId, price)`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
54
src/components/reservations/berth-reservations-list.tsx
Normal file
54
src/components/reservations/berth-reservations-list.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { PageHeader } from '@/components/shared/page-header';
|
||||
import { ReservationList, type ReservationRow } from '@/components/reservations/reservation-list';
|
||||
import { TableSkeleton } from '@/components/shared/loading-skeleton';
|
||||
import { apiFetch } from '@/lib/api/client';
|
||||
|
||||
interface ReservationsApiResponse {
|
||||
data: ReservationRow[];
|
||||
pagination: { total: number; page: number; pageSize: number };
|
||||
}
|
||||
|
||||
export function BerthReservationsList() {
|
||||
const params = useParams<{ portSlug: string }>();
|
||||
const portSlug = params?.portSlug ?? '';
|
||||
|
||||
const { data, isLoading } = useQuery<ReservationsApiResponse>({
|
||||
queryKey: ['berth-reservations', 'list'],
|
||||
queryFn: () => apiFetch('/api/v1/berth-reservations?page=1&limit=100&order=desc'),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<PageHeader
|
||||
eyebrow="Marina"
|
||||
title="Berth Reservations"
|
||||
description="All reservations across all berths"
|
||||
actions={
|
||||
<Link
|
||||
href={`/${portSlug}/berths`}
|
||||
className="text-sm text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
View berths
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
|
||||
{isLoading ? (
|
||||
<TableSkeleton />
|
||||
) : (
|
||||
<ReservationList
|
||||
reservations={data?.data ?? []}
|
||||
showBerth
|
||||
portSlug={portSlug}
|
||||
emptyMessage="No reservations found."
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user