feat(ui): company detail page with header, tabs, members, owned yachts

This commit is contained in:
Matt Ciaccio
2026-04-24 13:59:21 +02:00
parent d6743ed52c
commit 5d76a8a1cf
6 changed files with 842 additions and 0 deletions

View File

@@ -0,0 +1,167 @@
'use client';
import type { DetailTab } from '@/components/shared/detail-layout';
import { EmptyState } from '@/components/shared/empty-state';
import { CompanyMembersTab } from '@/components/companies/company-members-tab';
import { CompanyOwnedYachtsTab } from '@/components/companies/company-owned-yachts-tab';
interface CompanyTabsCompany {
id: string;
name: string;
legalName: string | null;
taxId: string | null;
registrationNumber: string | null;
incorporationCountry: string | null;
incorporationDate: string | null;
status: string;
billingEmail: string | null;
notes: string | null;
}
interface CompanyTabsOptions {
companyId: string;
portSlug: string;
currentUserId?: string;
company: CompanyTabsCompany;
}
const STATUS_LABELS: Record<string, string> = {
active: 'Active',
dissolved: 'Dissolved',
};
function InfoRow({ label, value }: { label: string; value?: string | number | null }) {
if (value === null || value === undefined || value === '') return null;
return (
<div className="flex gap-2 py-1.5 border-b last:border-0">
<dt className="w-40 shrink-0 text-sm text-muted-foreground">{label}</dt>
<dd className="text-sm">{value}</dd>
</div>
);
}
function formatDate(value: string | null): string | null {
if (!value) return null;
const date = new Date(value);
if (Number.isNaN(date.getTime())) return value;
return date.toLocaleDateString();
}
function OverviewTab({ company }: { company: CompanyTabsCompany }) {
const incorporationDate = formatDate(company.incorporationDate);
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Identity */}
<div className="space-y-1">
<h3 className="text-sm font-medium mb-2">Identity</h3>
<dl>
<InfoRow label="Name" value={company.name} />
<InfoRow label="Legal Name" value={company.legalName} />
<InfoRow label="Status" value={STATUS_LABELS[company.status] ?? company.status} />
</dl>
</div>
{/* Registration */}
{(company.taxId ||
company.registrationNumber ||
company.incorporationCountry ||
incorporationDate) && (
<div className="space-y-1">
<h3 className="text-sm font-medium mb-2">Registration</h3>
<dl>
<InfoRow label="Tax ID" value={company.taxId} />
<InfoRow label="Registration Number" value={company.registrationNumber} />
<InfoRow label="Incorporation Country" value={company.incorporationCountry} />
<InfoRow label="Incorporation Date" value={incorporationDate} />
</dl>
</div>
)}
{/* Contact */}
{company.billingEmail && (
<div className="space-y-1">
<h3 className="text-sm font-medium mb-2">Contact</h3>
<dl>
<InfoRow label="Billing Email" value={company.billingEmail} />
</dl>
</div>
)}
{/* Notes */}
{company.notes && (
<div className="space-y-1 md:col-span-2">
<h3 className="text-sm font-medium mb-2">Notes</h3>
<p className="text-sm whitespace-pre-wrap rounded-md border bg-muted/30 p-3">
{company.notes}
</p>
</div>
)}
</div>
);
}
export function getCompanyTabs({
companyId,
portSlug,
// currentUserId reserved for when NotesList supports entityType='companies'.
currentUserId: _currentUserId,
company,
}: CompanyTabsOptions): DetailTab[] {
void _currentUserId;
return [
{
id: 'overview',
label: 'Overview',
content: <OverviewTab company={company} />,
},
{
id: 'members',
label: 'Members',
content: <CompanyMembersTab companyId={companyId} portSlug={portSlug} />,
},
{
id: 'owned-yachts',
label: 'Owned Yachts',
content: <CompanyOwnedYachtsTab companyId={companyId} portSlug={portSlug} />,
},
{
id: 'addresses',
label: 'Addresses',
// TODO: wire to future company-addresses endpoint (see company-addresses schema).
content: (
<EmptyState
title="Addresses"
description="Company addresses coming soon — the addresses endpoint is pending wiring."
/>
),
},
{
id: 'documents',
label: 'Documents',
content: <EmptyState title="Documents" description="Coming soon" />,
},
{
id: 'notes',
label: 'Notes',
// TODO: NotesList currently supports entityType 'clients' | 'interests'.
// Extend NotesList (or swap to a company-notes endpoint) in a follow-up.
content: (
<EmptyState
title="Notes"
description="Company notes coming soon — the notes endpoint is pending wiring."
/>
),
},
{
id: 'tags',
label: 'Tags',
// TODO: replace with an inline tag editor once one exists; company tags
// can be edited via the Edit form in the meantime.
content: (
<EmptyState title="Tags" description="Manage tags from the Edit company form for now." />
),
},
];
}