Implements Task 5.3: server page passes yachtId to a client YachtDetail, which fetches via TanStack Query and renders the shared DetailLayout with Overview / Ownership History / Interests / Reservations / Notes / Tags tabs. Header shows name, dimensions, polymorphic owner link, status badge, and Edit / Transfer / Archive actions. Transfer is a stub dialog pending Task 5.5; Notes tab is a placeholder because NotesList does not yet support entityType='yachts'. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
124 lines
3.4 KiB
TypeScript
124 lines
3.4 KiB
TypeScript
'use client';
|
|
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import { useParams } from 'next/navigation';
|
|
import { Loader2 } from 'lucide-react';
|
|
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from '@/components/ui/table';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { EmptyState } from '@/components/shared/empty-state';
|
|
import { OwnerLink } from '@/components/yachts/yacht-detail-header';
|
|
import { apiFetch } from '@/lib/api/client';
|
|
|
|
interface OwnershipHistoryRow {
|
|
id: string;
|
|
yachtId: string;
|
|
ownerType: 'client' | 'company';
|
|
ownerId: string;
|
|
startDate: string;
|
|
endDate: string | null;
|
|
transferReason: string | null;
|
|
transferNotes: string | null;
|
|
createdBy: string;
|
|
createdAt: string;
|
|
}
|
|
|
|
interface YachtOwnershipHistoryProps {
|
|
yachtId: string;
|
|
}
|
|
|
|
const REASON_LABELS: Record<string, string> = {
|
|
sale: 'Sale',
|
|
inheritance: 'Inheritance',
|
|
gift: 'Gift',
|
|
company_restructure: 'Company restructure',
|
|
other: 'Other',
|
|
};
|
|
|
|
function formatDate(value: string | null): string {
|
|
if (!value) return '—';
|
|
const date = new Date(value);
|
|
if (Number.isNaN(date.getTime())) return value;
|
|
return date.toLocaleDateString();
|
|
}
|
|
|
|
export function YachtOwnershipHistory({ yachtId }: YachtOwnershipHistoryProps) {
|
|
const params = useParams<{ portSlug: string }>();
|
|
const portSlug = params?.portSlug ?? '';
|
|
|
|
const { data, isLoading } = useQuery<OwnershipHistoryRow[]>({
|
|
queryKey: ['yachts', yachtId, 'ownership-history'],
|
|
queryFn: () =>
|
|
apiFetch<{ data: OwnershipHistoryRow[] }>(`/api/v1/yachts/${yachtId}/ownership-history`).then(
|
|
(r) => r.data,
|
|
),
|
|
});
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex items-center justify-center py-12">
|
|
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!data || data.length === 0) {
|
|
return (
|
|
<EmptyState
|
|
title="No ownership history"
|
|
description="This yacht's ownership transfers will appear here."
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="rounded-md border">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Start Date</TableHead>
|
|
<TableHead>End Date</TableHead>
|
|
<TableHead>Owner</TableHead>
|
|
<TableHead>Reason</TableHead>
|
|
<TableHead>Notes</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{data.map((row) => (
|
|
<TableRow key={row.id}>
|
|
<TableCell>{formatDate(row.startDate)}</TableCell>
|
|
<TableCell>
|
|
{row.endDate ? (
|
|
formatDate(row.endDate)
|
|
) : (
|
|
<Badge variant="secondary" className="text-xs">
|
|
Current
|
|
</Badge>
|
|
)}
|
|
</TableCell>
|
|
<TableCell>
|
|
<OwnerLink portSlug={portSlug} type={row.ownerType} id={row.ownerId} />
|
|
</TableCell>
|
|
<TableCell className="text-sm text-muted-foreground">
|
|
{row.transferReason
|
|
? (REASON_LABELS[row.transferReason] ?? row.transferReason)
|
|
: '—'}
|
|
</TableCell>
|
|
<TableCell className="text-sm text-muted-foreground max-w-[320px] truncate">
|
|
{row.transferNotes ?? '—'}
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
);
|
|
}
|