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

@@ -1,4 +1,4 @@
import { and, eq, ilike, or, sql } from 'drizzle-orm';
import { and, eq, ilike, inArray, or, sql } from 'drizzle-orm';
import { db } from '@/lib/db';
import { yachts, yachtOwnershipHistory, yachtTags, clients } from '@/lib/db/schema';
import type { Yacht } from '@/lib/db/schema/yachts';
@@ -307,7 +307,45 @@ export async function listYachts(portId: string, query: ListYachtsInput) {
archivedAtColumn: yachts.archivedAt,
});
return result;
if (result.data.length === 0) return result;
// Resolve current owner names in two parallel batched queries instead of
// an N+1 fetch from the client (was 1 round-trip per row from yacht-columns).
const clientIds = result.data
.filter((y) => y.currentOwnerType === 'client')
.map((y) => y.currentOwnerId);
const companyIds = result.data
.filter((y) => y.currentOwnerType === 'company')
.map((y) => y.currentOwnerId);
const [clientRows, companyRows] = await Promise.all([
clientIds.length > 0
? db
.select({ id: clients.id, fullName: clients.fullName })
.from(clients)
.where(inArray(clients.id, clientIds))
: Promise.resolve([] as { id: string; fullName: string }[]),
companyIds.length > 0
? db
.select({ id: companies.id, name: companies.name })
.from(companies)
.where(inArray(companies.id, companyIds))
: Promise.resolve([] as { id: string; name: string }[]),
]);
const clientNames = new Map(clientRows.map((r) => [r.id, r.fullName]));
const companyNames = new Map(companyRows.map((r) => [r.id, r.name]));
return {
...result,
data: result.data.map((y) => ({
...y,
currentOwnerName:
y.currentOwnerType === 'client'
? (clientNames.get(y.currentOwnerId) ?? null)
: (companyNames.get(y.currentOwnerId) ?? null),
})),
};
}
// ─── List for owner ───────────────────────────────────────────────────────────