From b703684285d0426c03bc36e1459c60e184f018c1 Mon Sep 17 00:00:00 2001 From: Matt Ciaccio Date: Sun, 3 May 2026 17:09:27 +0200 Subject: [PATCH] =?UTF-8?q?fix(ux):=20pass-3=20=E2=80=94=20yacht/company?= =?UTF-8?q?=20headers,=20reminder=20filters=20wrap,=20client=20tab=20count?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five small fixes from the third audit pass on previously-unchecked surfaces: Yacht detail header (mobile): - Stack the action cluster (Edit / Transfer / Archive) below the title block on phone widths. Previously the three buttons crowded the right side enough to truncate the status pill to "A..." and force the owner name to wrap to two lines. Same fix that landed for berth / client / company headers. Company detail header (mobile): - Same mobile stacking fix; legal-name + Tax-ID metadata no longer wraps awkwardly. Company detail Incorporation Date (all viewports): - Strip the time portion of the ISO timestamp before passing to the inline editor. Previously rendered the raw "2019-03-14T00:00:00.000Z" Postgres-serialized form. Now reads "2019-03-14" and round-trips through the YYYY-MM-DD inline editor cleanly. Reminders list filter row: - Allow flex-wrap on the My/All tabs + status filter + priority filter cluster. At 390px, the priority filter dropdown was being pushed off the right edge of the screen. Client detail tab counts: - Add interestCount + noteCount to getClientById response, surface as badges on the Interests + Notes tabs. Brings them into parity with Yachts/Companies/Reservations/Addresses which already showed counts; Files + Activity are still stubs and don't get a count yet. Verification: 0 tsc errors, 926/926 vitest passing, lint clean. Out of scope (deferred): - Residential clients / interests pages still render plain HTML tables on phone widths (header columns clip at the right edge). Needs the DataView card-on-mobile treatment that the main /clients and /interests pages already have. Substantial separate work. - Phone contacts in the legacy seed have value set but valueE164 NULL, so InlinePhoneField shows "—" even though metadata is technically populated. Fix is a one-time backfill via libphonenumber-js, not a UI change. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/components/clients/client-tabs.tsx | 4 ++++ .../companies/company-detail-header.tsx | 4 +++- src/components/companies/company-tabs.tsx | 6 +++++- src/components/reminders/reminder-list.tsx | 4 +++- src/components/yachts/yacht-detail-header.tsx | 5 ++++- src/lib/services/clients.service.ts | 16 ++++++++++++++++ 6 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/components/clients/client-tabs.tsx b/src/components/clients/client-tabs.tsx index 9764650..b4ca8e4 100644 --- a/src/components/clients/client-tabs.tsx +++ b/src/components/clients/client-tabs.tsx @@ -116,6 +116,8 @@ interface ClientTabsOptions { tenureType: string; status: string; }>; + interestCount?: number; + noteCount?: number; tags?: Array<{ id: string; name: string; color: string }>; }; } @@ -224,6 +226,7 @@ export function getClientTabs({ clientId, currentUserId, client }: ClientTabsOpt { id: 'interests', label: 'Interests', + badge: client.interestCount, content: , }, { @@ -261,6 +264,7 @@ export function getClientTabs({ clientId, currentUserId, client }: ClientTabsOpt { id: 'notes', label: 'Notes', + badge: client.noteCount, content: , }, { diff --git a/src/components/companies/company-detail-header.tsx b/src/components/companies/company-detail-header.tsx index b7f0f9f..228e31a 100644 --- a/src/components/companies/company-detail-header.tsx +++ b/src/components/companies/company-detail-header.tsx @@ -77,7 +77,9 @@ export function CompanyDetailHeader({ company }: CompanyDetailHeaderProps) { return ( <> -
+ {/* Stack actions below the title block on phone widths; horizontal + beside it from sm up. */} +

diff --git a/src/components/companies/company-tabs.tsx b/src/components/companies/company-tabs.tsx index 4a5feaa..4fa2b44 100644 --- a/src/components/companies/company-tabs.tsx +++ b/src/components/companies/company-tabs.tsx @@ -146,7 +146,11 @@ function OverviewTab({ companyId, company }: { companyId: string; company: Compa diff --git a/src/components/reminders/reminder-list.tsx b/src/components/reminders/reminder-list.tsx index b9800e9..37761c7 100644 --- a/src/components/reminders/reminder-list.tsx +++ b/src/components/reminders/reminder-list.tsx @@ -249,7 +249,9 @@ export function ReminderList() { } /> -
+ {/* Wrap on phone widths so the priority filter doesn't get pushed + off-screen by the My/All tabs + status filter taking the full row. */} +
{canViewAll && ( setViewMode(v as 'my' | 'all')}> diff --git a/src/components/yachts/yacht-detail-header.tsx b/src/components/yachts/yacht-detail-header.tsx index 2a60245..5306396 100644 --- a/src/components/yachts/yacht-detail-header.tsx +++ b/src/components/yachts/yacht-detail-header.tsx @@ -142,7 +142,10 @@ export function YachtDetailHeader({ yacht }: YachtDetailHeaderProps) { return ( <> -
+ {/* Stacks vertically on phone widths so the action cluster doesn't + crush the status pill / owner row. From sm up, title block sits + beside actions in the original layout. */} +

diff --git a/src/lib/services/clients.service.ts b/src/lib/services/clients.service.ts index 7bfc5ac..290c2e1 100644 --- a/src/lib/services/clients.service.ts +++ b/src/lib/services/clients.service.ts @@ -4,6 +4,7 @@ import { db } from '@/lib/db'; import { clients, clientContacts, + clientNotes, clientRelationships, clientTags, clientAddresses, @@ -251,6 +252,19 @@ export async function getClientById(id: string, portId: string) { const portalEnabled = await isPortalEnabledForPort(portId); + // Counts surfaced for tab badges (Interests + Notes — Yachts/Companies/etc + // get their counts from the corresponding row arrays we already fetched). + const [interestCountRow] = await db + .select({ count: count() }) + .from(interests) + .where( + and(eq(interests.portId, portId), eq(interests.clientId, id), isNull(interests.archivedAt)), + ); + const [noteCountRow] = await db + .select({ count: count() }) + .from(clientNotes) + .where(eq(clientNotes.clientId, id)); + return { ...client, contacts, @@ -259,6 +273,8 @@ export async function getClientById(id: string, portId: string) { yachts: yachtRows, companies: membershipRows, activeReservations, + interestCount: interestCountRow?.count ?? 0, + noteCount: noteCountRow?.count ?? 0, clientPortalEnabled: portalEnabled, }; }