fix(ui): resolve yacht owner names server-side, real user in topbar
All checks were successful
Build & Push Docker Images / lint (pull_request) Successful in 1m1s
Build & Push Docker Images / build-and-push (pull_request) Has been skipped

Yachts list page rendered each row's Current Owner via OwnerLink, which
fired its own /api/v1/clients/{id} or /companies/{id} fetch — N+1 round-
trips per page load (12+ for the harbor-royale fixture). Worse, until
those fetches resolved each cell showed "Client c68da7..." style raw IDs.

Fix: listYachts now resolves the polymorphic currentOwnerName in two
batched in-array queries after the page query (mirrors the listClients
yachtCount/companyCount pattern), and OwnerLink accepts an optional
preloadedName prop that suppresses the per-row fetch when supplied.

Topbar: show real user name + avatar initial from session/profile, and
expand the My-Account dropdown header to include the user's email.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Ciaccio
2026-04-27 23:54:04 +02:00
parent 0ccc66833d
commit 1791dd7319
5 changed files with 67 additions and 8 deletions

View File

@@ -20,6 +20,7 @@ export interface YachtRow {
registration: string | null;
currentOwnerType: 'client' | 'company';
currentOwnerId: string;
currentOwnerName?: string | null;
lengthFt: string | null;
widthFt: string | null;
draftFt: string | null;
@@ -92,6 +93,7 @@ export function getYachtColumns({
portSlug={portSlug}
type={row.original.currentOwnerType}
id={row.original.currentOwnerId}
preloadedName={row.original.currentOwnerName ?? null}
/>
),
},

View File

@@ -58,20 +58,27 @@ export function OwnerLink({
portSlug,
type,
id,
preloadedName,
}: {
portSlug: string;
type: 'client' | 'company';
id: string;
/** Optional name supplied by the parent list/detail endpoint to skip the
* per-row fetch (avoids an N+1 round-trip on lists). */
preloadedName?: string | null;
}) {
// Only fetch when the parent didn't already supply a name — list endpoints
// batch-resolve owners server-side via a join.
const { data } = useQuery<{ fullName?: string; name?: string }>({
queryKey: [type === 'client' ? 'clients' : 'companies', id, 'name-only'],
queryFn: () =>
apiFetch<{ data: { fullName?: string; name?: string } }>(
type === 'client' ? `/api/v1/clients/${id}` : `/api/v1/companies/${id}`,
).then((r) => r.data),
enabled: preloadedName === undefined || preloadedName === null,
});
const label = type === 'client' ? data?.fullName : data?.name;
const label = preloadedName ?? (type === 'client' ? data?.fullName : data?.name);
const href = type === 'client' ? `/${portSlug}/clients/${id}` : `/${portSlug}/companies/${id}`;
return (