From 05257723f6bbfa8daf732a647868d5d44169172a Mon Sep 17 00:00:00 2001 From: Matt Ciaccio Date: Tue, 5 May 2026 02:18:13 +0200 Subject: [PATCH] fix(interests): list yacht join + EOI status column + col redesign MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire interests.yachtId -> yachts.name into the listInterests post-fetch enrichment so the redesigned columns (Client · Yacht · Berth · Stage · EOI status · Source · Last activity) render the linked yacht. - Add yachtId/yachtName to InterestRow. - listInterests: fourth parallel join for yachts.name, Map merged alongside the existing client/berth/tag/notes joins. - interest-columns: add Yacht column (with link to /yachts/[id] when the yacht has an id); replace Category with EOI status (badge driven by interests.eoi_status); drop default-view Tags. The "Berth size desired" column called out in §5.2 is deferred to Phase 2 since the underlying desired_*_ft columns don't exist yet. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/components/interests/interest-columns.tsx | 79 ++++++++++--------- src/lib/services/interests.service.ts | 19 ++++- 2 files changed, 61 insertions(+), 37 deletions(-) diff --git a/src/components/interests/interest-columns.tsx b/src/components/interests/interest-columns.tsx index 9be0189..3f523d8 100644 --- a/src/components/interests/interest-columns.tsx +++ b/src/components/interests/interest-columns.tsx @@ -13,7 +13,6 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { Badge } from '@/components/ui/badge'; -import { TagBadge } from '@/components/shared/tag-badge'; import { stageBadgeClass, stageLabel } from '@/lib/constants'; import { computeUrgencyBadges, type InterestUrgencyInput } from '@/components/interests/urgency'; @@ -21,6 +20,8 @@ export interface InterestRow { id: string; clientId: string; clientName: string | null; + yachtId?: string | null; + yachtName?: string | null; berthId: string | null; berthMooringNumber: string | null; pipelineStage: string; @@ -40,12 +41,6 @@ export interface InterestRow { tags?: Array<{ id: string; name: string; color: string }>; } -const CATEGORY_LABELS: Record = { - general_interest: 'General Interest', - specific_qualified: 'Specific Qualified', - hot_lead: 'Hot Lead', -}; - const SOURCE_LABELS: Record = { website: 'Website', manual: 'Manual', @@ -53,6 +48,12 @@ const SOURCE_LABELS: Record = { broker: 'Broker', }; +const EOI_STATUS_LABELS: Record = { + waiting_for_signatures: { label: 'Waiting', tone: 'bg-amber-100 text-amber-900' }, + signed: { label: 'Signed', tone: 'bg-emerald-100 text-emerald-900' }, + expired: { label: 'Expired', tone: 'bg-rose-100 text-rose-900' }, +}; + interface GetColumnsOptions { portSlug: string; onEdit: (interest: InterestRow) => void; @@ -93,6 +94,27 @@ export function getInterestColumns({ ); }, }, + { + id: 'yachtName', + accessorKey: 'yachtName', + header: 'Yacht', + enableSorting: false, + cell: ({ row }) => { + const name = row.original.yachtName; + if (!name) return -; + const yachtId = row.original.yachtId; + if (!yachtId) return {name}; + return ( + e.stopPropagation()} + > + {name} + + ); + }, + }, { id: 'berthMooringNumber', accessorKey: 'berthMooringNumber', @@ -145,16 +167,22 @@ export function getInterestColumns({ }, }, { - id: 'leadCategory', - accessorKey: 'leadCategory', - header: 'Category', + id: 'eoiStatus', + accessorKey: 'eoiStatus', + header: 'EOI status', + enableSorting: false, cell: ({ getValue }) => { - const cat = getValue() as string | null; - if (!cat) return -; + const status = getValue() as string | null; + if (!status) return -; + const meta = EOI_STATUS_LABELS[status]; return ( - - {CATEGORY_LABELS[cat] ?? cat} - + + {meta?.label ?? status} + ); }, }, @@ -172,27 +200,6 @@ export function getInterestColumns({ ); }, }, - { - id: 'tags', - header: 'Tags', - enableSorting: false, - cell: ({ row }) => { - const rowTags = row.original.tags ?? []; - if (rowTags.length === 0) return -; - return ( -
- {rowTags.slice(0, 3).map((tag) => ( - - ))} - {rowTags.length > 3 && ( - - +{rowTags.length - 3} - - )} -
- ); - }, - }, { // Sales-triage default: prefer the explicit dateLastContact, fall back // to updatedAt. Sortable on dateLastContact server-side; the column diff --git a/src/lib/services/interests.service.ts b/src/lib/services/interests.service.ts index 04512b1..4b32bc4 100644 --- a/src/lib/services/interests.service.ts +++ b/src/lib/services/interests.service.ts @@ -209,7 +209,7 @@ export async function listInterests(portId: string, query: ListInterestsInput) { archivedAtColumn: interests.archivedAt, }); - // Join client names and berth mooring numbers + // Join client names, berth mooring numbers, and yacht names. const interestIds = ( result.data as Array<{ id: string; clientId: string; berthId: string | null }> ).map((i) => i.id); @@ -223,9 +223,17 @@ export async function listInterests(portId: string, query: ListInterestsInput) { .filter(Boolean) as string[], ), ]; + const yachtIds = [ + ...new Set( + (result.data as Array<{ yachtId: string | null }>) + .map((i) => i.yachtId) + .filter(Boolean) as string[], + ), + ]; let clientsMap: Record = {}; let berthsMap: Record = {}; + let yachtsMap: Record = {}; const tagsByInterestId: Record> = {}; const notesCountByInterestId: Record = {}; @@ -245,6 +253,14 @@ export async function listInterests(portId: string, query: ListInterestsInput) { berthsMap = Object.fromEntries(berthRows.map((b) => [b.id, b.mooringNumber])); } + if (yachtIds.length > 0) { + const yachtRows = await db + .select({ id: yachts.id, name: yachts.name }) + .from(yachts) + .where(inArray(yachts.id, yachtIds)); + yachtsMap = Object.fromEntries(yachtRows.map((y) => [y.id, y.name])); + } + if (interestIds.length > 0) { const tagRows = await db .select({ @@ -280,6 +296,7 @@ export async function listInterests(portId: string, query: ListInterestsInput) { ...i, clientName: clientsMap[i.clientId as string] ?? null, berthMooringNumber: i.berthId ? (berthsMap[i.berthId as string] ?? null) : null, + yachtName: i.yachtId ? (yachtsMap[i.yachtId as string] ?? null) : null, tags: tagsByInterestId[i.id as string] ?? [], notesCount: notesCountByInterestId[i.id as string] ?? 0, }));