'use client'; import { useQuery } from '@tanstack/react-query'; import { AlertTriangle } from 'lucide-react'; import { apiFetch } from '@/lib/api/client'; interface BerthRow { id: string; mooringNumber: string; status: string; isPrimary: boolean; } interface BerthsResponse { data: BerthRow[]; } /** * Surfaces when one of the interest's linked berths is sold or under offer * to a different deal. We don't block the rep from proceeding (the user * explicitly wanted v1 to still let the deal advance — the assumption is * that the rep is aware and treating the current deal as a fallback if * the other one falls through), but the banner makes the conflict visible * so they aren't surprised when the rules engine flags it. * * Fires only for active (non-archived, non-closed) interests — banners on * lost deals are noise. */ export function InterestBerthStatusBanner({ interestId, interestPipelineStage, interestOutcome, archivedAt, }: { interestId: string; interestPipelineStage: string; interestOutcome?: string | null; archivedAt?: string | null; }) { const { data } = useQuery({ queryKey: ['interest-berths', interestId], queryFn: () => apiFetch(`/api/v1/interests/${interestId}/berths`), }); if (archivedAt || interestOutcome) return null; // The banner is most useful before the rep is committed to the deal — // once contract is in motion, the conflict is moot. if (interestPipelineStage === 'contract') return null; const berths = data?.data ?? []; const conflicts = berths.filter((b) => b.status === 'sold' || b.status === 'under_offer'); if (conflicts.length === 0) return null; return (

{conflicts.length === 1 ? `Berth ${conflicts[0]!.mooringNumber} is ${ conflicts[0]!.status === 'sold' ? 'Sold' : 'Under Offer' } to another deal.` : `${conflicts.length} linked berths are no longer freely available.`}

You can still progress this interest as a backup, but the rep on the other deal owns the primary path. If their deal falls through, this one can step in.

); }