refactor(clients): rebuild detail tabs + columns for new data model

- ClientData in client-detail.tsx now reflects the stripped shape from
  Task 8.2 (drop companyName/isProxy/proxy*/yacht*/berthSizeDesired) and
  gains yachts / companies / activeReservations arrays.
- client-tabs.tsx: Overview trimmed (personal, contacts, source, tags);
  three new count-badged tabs (Yachts, Companies, Reservations).
- New client-yachts-tab.tsx renders owned yachts + Add yacht CTA (TODO:
  YachtForm preset-owner wiring for v2).
- New client-companies-tab.tsx renders memberships with Primary badge and
  since-date; management still lives on the company detail page.
- New client-reservations-tab.tsx maps activeReservations into ReservationRow
  shape and delegates to <ReservationList showBerth />.
- client-columns.tsx drops companyName column (TODO: add Yachts count +
  Primary company once list endpoint joins those).
- client-filters.tsx drops isProxy filter.
- Wire realtime invalidations for yacht:ownership_transferred,
  company_membership:added/ended, and berth_reservation:*.
This commit is contained in:
Matt Ciaccio
2026-04-24 14:36:34 +02:00
parent 4c171848fc
commit b75834ab7e
7 changed files with 374 additions and 113 deletions

View File

@@ -2,22 +2,16 @@
import type { DetailTab } from '@/components/shared/detail-layout';
import { NotesList } from '@/components/shared/notes-list';
import { ClientYachtsTab } from '@/components/clients/client-yachts-tab';
import { ClientCompaniesTab } from '@/components/clients/client-companies-tab';
import { ClientReservationsTab } from '@/components/clients/client-reservations-tab';
interface ClientTabsOptions {
clientId: string;
currentUserId?: string;
client: {
fullName: string;
companyName?: string | null;
nationality?: string | null;
isProxy?: boolean;
proxyType?: string | null;
actualOwnerName?: string | null;
yachtName?: string | null;
yachtLengthFt?: string | null;
yachtWidthFt?: string | null;
yachtDraftFt?: string | null;
berthSizeDesired?: string | null;
preferredContactMethod?: string | null;
preferredLanguage?: string | null;
timezone?: string | null;
@@ -30,6 +24,36 @@ interface ClientTabsOptions {
label?: string | null;
isPrimary: boolean;
}>;
yachts: Array<{
id: string;
name: string;
hullNumber: string | null;
registration: string | null;
lengthFt: string | null;
widthFt: string | null;
status: string;
}>;
companies: Array<{
membershipId: string;
role: string;
isPrimary: boolean;
startDate: string | Date;
company: {
id: string;
name: string;
legalName: string | null;
status: string;
};
}>;
activeReservations: Array<{
id: string;
berthId: string;
yachtId: string;
startDate: string | Date;
tenureType: string;
status: string;
}>;
tags?: Array<{ id: string; name: string; color: string }>;
};
}
@@ -51,14 +75,10 @@ function OverviewTab({ client }: { client: ClientTabsOptions['client'] }) {
<h3 className="text-sm font-medium mb-2">Personal Information</h3>
<dl>
<InfoRow label="Full Name" value={client.fullName} />
<InfoRow label="Company" value={client.companyName} />
<InfoRow label="Nationality" value={client.nationality} />
<InfoRow label="Preferred Language" value={client.preferredLanguage} />
<InfoRow label="Timezone" value={client.timezone} />
<InfoRow
label="Preferred Contact"
value={client.preferredContactMethod}
/>
<InfoRow label="Preferred Contact" value={client.preferredContactMethod} />
</dl>
</div>
@@ -72,18 +92,12 @@ function OverviewTab({ client }: { client: ClientTabsOptions['client'] }) {
key={c.id}
className="flex items-center gap-2 p-2 rounded-lg border bg-muted/30 text-sm"
>
<span className="capitalize text-muted-foreground w-20 shrink-0">
{c.channel}
</span>
<span className="capitalize text-muted-foreground w-20 shrink-0">{c.channel}</span>
<span className="flex-1">{c.value}</span>
{c.label && (
<span className="text-xs text-muted-foreground capitalize">
{c.label}
</span>
)}
{c.isPrimary && (
<span className="text-xs font-medium text-primary">Primary</span>
<span className="text-xs text-muted-foreground capitalize">{c.label}</span>
)}
{c.isPrimary && <span className="text-xs font-medium text-primary">Primary</span>}
</div>
))}
</div>
@@ -92,41 +106,6 @@ function OverviewTab({ client }: { client: ClientTabsOptions['client'] }) {
)}
</div>
{/* Yacht Details */}
{(client.yachtName ||
client.yachtLengthFt ||
client.berthSizeDesired) && (
<div className="space-y-1">
<h3 className="text-sm font-medium mb-2">Yacht Details</h3>
<dl>
<InfoRow label="Yacht Name" value={client.yachtName} />
<InfoRow
label="Length"
value={
client.yachtLengthFt
? `${client.yachtLengthFt} ft`
: undefined
}
/>
<InfoRow
label="Width"
value={
client.yachtWidthFt ? `${client.yachtWidthFt} ft` : undefined
}
/>
<InfoRow
label="Draft"
value={
client.yachtDraftFt
? `${client.yachtDraftFt} ft`
: undefined
}
/>
<InfoRow label="Berth Size Desired" value={client.berthSizeDesired} />
</dl>
</div>
)}
{/* Source */}
{(client.source || client.sourceDetails) && (
<div className="space-y-1">
@@ -138,34 +117,54 @@ function OverviewTab({ client }: { client: ClientTabsOptions['client'] }) {
</div>
)}
{/* Proxy Info */}
{client.isProxy && (
{/* Tags */}
{client.tags && client.tags.length > 0 && (
<div className="space-y-1">
<h3 className="text-sm font-medium mb-2">Proxy Information</h3>
<dl>
<InfoRow
label="Proxy Type"
value={client.proxyType?.replace('_', ' ')}
/>
<InfoRow label="Actual Owner" value={client.actualOwnerName} />
</dl>
<h3 className="text-sm font-medium mb-2">Tags</h3>
<div className="flex flex-wrap gap-1">
{client.tags.map((tag) => (
<span
key={tag.id}
className="inline-block rounded-full px-2 py-0.5 text-xs font-medium"
style={{ backgroundColor: `${tag.color}20`, color: tag.color }}
>
{tag.name}
</span>
))}
</div>
</div>
)}
</div>
);
}
export function getClientTabs({
clientId,
currentUserId,
client,
}: ClientTabsOptions): DetailTab[] {
export function getClientTabs({ clientId, currentUserId, client }: ClientTabsOptions): DetailTab[] {
return [
{
id: 'overview',
label: 'Overview',
content: <OverviewTab client={client} />,
},
{
id: 'yachts',
label: 'Yachts',
badge: client.yachts.length,
content: <ClientYachtsTab clientId={clientId} yachts={client.yachts} />,
},
{
id: 'companies',
label: 'Companies',
badge: client.companies.length,
content: <ClientCompaniesTab clientId={clientId} companies={client.companies} />,
},
{
id: 'reservations',
label: 'Reservations',
badge: client.activeReservations.length,
content: (
<ClientReservationsTab clientId={clientId} activeReservations={client.activeReservations} />
),
},
{
id: 'interests',
label: 'Interests',
@@ -178,13 +177,7 @@ export function getClientTabs({
{
id: 'notes',
label: 'Notes',
content: (
<NotesList
entityType="clients"
entityId={clientId}
currentUserId={currentUserId}
/>
),
content: <NotesList entityType="clients" entityId={clientId} currentUserId={currentUserId} />,
},
{
id: 'files',