feat(ui): company detail page with header, tabs, members, owned yachts
This commit is contained in:
156
src/components/companies/company-owned-yachts-tab.tsx
Normal file
156
src/components/companies/company-owned-yachts-tab.tsx
Normal file
@@ -0,0 +1,156 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import { EmptyState } from '@/components/shared/empty-state';
|
||||
import { apiFetch } from '@/lib/api/client';
|
||||
|
||||
interface OwnedYachtRow {
|
||||
id: string;
|
||||
name: string;
|
||||
hullNumber: string | null;
|
||||
lengthFt: string | null;
|
||||
widthFt: string | null;
|
||||
lengthM: string | null;
|
||||
widthM: string | null;
|
||||
status: string;
|
||||
}
|
||||
|
||||
interface YachtListResponse {
|
||||
data: OwnedYachtRow[];
|
||||
}
|
||||
|
||||
interface CompanyOwnedYachtsTabProps {
|
||||
companyId: string;
|
||||
portSlug: string;
|
||||
}
|
||||
|
||||
const STATUS_COLORS: Record<string, string> = {
|
||||
active: 'bg-green-100 text-green-800 border-green-300',
|
||||
retired: 'bg-gray-100 text-gray-800 border-gray-300',
|
||||
sold_away: 'bg-amber-100 text-amber-800 border-amber-300',
|
||||
};
|
||||
|
||||
const STATUS_LABELS: Record<string, string> = {
|
||||
active: 'Active',
|
||||
retired: 'Retired',
|
||||
sold_away: 'Sold Away',
|
||||
};
|
||||
|
||||
function formatDimensions(y: OwnedYachtRow): string | null {
|
||||
if (y.lengthFt || y.widthFt) {
|
||||
const length = y.lengthFt ?? '—';
|
||||
const width = y.widthFt ?? '—';
|
||||
return `${length} × ${width} ft`;
|
||||
}
|
||||
if (y.lengthM || y.widthM) {
|
||||
const length = y.lengthM ?? '—';
|
||||
const width = y.widthM ?? '—';
|
||||
return `${length} × ${width} m`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function CompanyOwnedYachtsTab({ companyId, portSlug }: CompanyOwnedYachtsTabProps) {
|
||||
const { data, isLoading } = useQuery<OwnedYachtRow[]>({
|
||||
queryKey: ['companies', companyId, 'owned-yachts'],
|
||||
queryFn: async () => {
|
||||
const params = new URLSearchParams({
|
||||
ownerType: 'company',
|
||||
ownerId: companyId,
|
||||
page: '1',
|
||||
limit: '50',
|
||||
includeArchived: 'false',
|
||||
order: 'desc',
|
||||
});
|
||||
const res = await apiFetch<YachtListResponse>(`/api/v1/yachts?${params.toString()}`);
|
||||
return res.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>
|
||||
);
|
||||
}
|
||||
|
||||
const yachts = data ?? [];
|
||||
|
||||
if (yachts.length === 0) {
|
||||
return (
|
||||
<EmptyState
|
||||
title="No yachts owned"
|
||||
description="Yachts owned by this company will appear here."
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Dimensions</TableHead>
|
||||
<TableHead>Hull Number</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{yachts.map((y) => {
|
||||
const dims = formatDimensions(y);
|
||||
const statusLabel = STATUS_LABELS[y.status] ?? y.status;
|
||||
const statusColor =
|
||||
STATUS_COLORS[y.status] ?? 'bg-muted text-muted-foreground border-muted';
|
||||
return (
|
||||
<TableRow key={y.id}>
|
||||
<TableCell>
|
||||
<Link
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
href={`/${portSlug}/yachts/${y.id}` as any}
|
||||
className="font-medium text-primary hover:underline"
|
||||
>
|
||||
{y.name}
|
||||
</Link>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{dims ? (
|
||||
<span className="text-sm">{dims}</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground">—</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{y.hullNumber ? (
|
||||
<span className="text-sm">{y.hullNumber}</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground">—</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span
|
||||
className={`inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium ${statusColor}`}
|
||||
>
|
||||
{statusLabel}
|
||||
</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user