'use client'; import Link from 'next/link'; import { useQuery } from '@tanstack/react-query'; import { apiFetch } from '@/lib/api/client'; import { stageBadgeClass, stageLabel } from '@/lib/constants'; import { cn } from '@/lib/utils'; interface ActiveInterestRow { interestId: string; clientName: string; pipelineStage: string; isPrimary: boolean; isInEoiBundle: boolean; } interface BerthOccupancyChipProps { /** Berth to query. */ berthId: string; /** Port slug for the competing-interest link. */ portSlug: string; /** Optional: hide rows from this interest (so a "competing" chip on * a row inside Interest A doesn't surface A itself). */ excludeInterestId?: string | null; /** Hide the chip entirely when the berth has zero active interests * (default: true). Set false when the parent wants a render even * for available berths — useful for the linked-berth row where the * rep wants explicit "no competing interest" feedback. */ hideWhenEmpty?: boolean; /** Compact variant — single-line chip with truncation. Default * shows on multiple lines when the client name overflows. */ compact?: boolean; } /** * Surfaces the competing interest(s) that own a non-available berth. * Reuses /api/v1/berths/[id]/active-interests (shipped for the columns * popover) so the data path is consistent across: * - LinkedBerthRowItem (per linked berth on the interest detail) * - BerthRecommenderPanel recommendation card body * - InterestBerthStatusBanner (deal-level banner) * * Renders the highest-priority competing interest (in-EOI-bundle first, * then primary, then most-recently-updated). Clicking the chip * navigates to the competing interest's detail page. */ export function BerthOccupancyChip({ berthId, portSlug, excludeInterestId, hideWhenEmpty = true, compact = false, }: BerthOccupancyChipProps) { const { data, isLoading } = useQuery<{ data: ActiveInterestRow[] }>({ queryKey: ['berth', berthId, 'active-interests'], queryFn: () => apiFetch<{ data: ActiveInterestRow[] }>(`/api/v1/berths/${berthId}/active-interests`), staleTime: 30_000, }); const rows = data?.data ?? []; const competing = rows.filter((r) => excludeInterestId ? r.interestId !== excludeInterestId : true, ); if (isLoading) return null; if (competing.length === 0 && hideWhenEmpty) return null; if (competing.length === 0) { return ( No competing interest ); } // Priority: in-EOI-bundle (committed) > primary (flagged primary) > // first by API order (already most-recently-updated server-side). const primary = competing.find((r) => r.isInEoiBundle) ?? competing.find((r) => r.isPrimary) ?? competing[0]!; const extras = competing.length - 1; return ( e.stopPropagation()} className={cn( 'inline-flex items-center gap-1.5 rounded-md border border-amber-300 bg-amber-50 px-2 py-0.5 text-xs text-amber-900 hover:bg-amber-100 transition-colors', // Cap tight on narrow viewports, but give the name room on desktop // so it isn't truncated to "Philippe Ca…" (UAT 2026-06-03). compact && 'max-w-[200px] md:max-w-[460px]', )} title={`Open ${primary.clientName} (${stageLabel(primary.pipelineStage)})`} > Under offer to: {primary.clientName} {stageLabel(primary.pipelineStage)} {extras > 0 ? +{extras} more : null} ); }