'use client'; import { useMemo, useState } from 'react'; import Link from 'next/link'; import { useParams } from 'next/navigation'; import { useQuery } from '@tanstack/react-query'; import { apiFetch } from '@/lib/api/client'; import { useRealtimeInvalidation } from '@/hooks/use-realtime-invalidation'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { TableSkeleton } from '@/components/shared/loading-skeleton'; import { EmptyState } from '@/components/shared/empty-state'; import { Bookmark } from 'lucide-react'; import { PIPELINE_STAGES, stageLabel, formatSource } from '@/lib/constants'; import type { InterestRow } from '@/components/interests/interest-columns'; interface BerthInterestsTabProps { berthId: string; } type StageFilter = 'all' | 'active' | 'lost'; type SortMode = 'newest' | 'stage' | 'category'; function stageRank(stage: string): number { const idx = PIPELINE_STAGES.indexOf(stage as (typeof PIPELINE_STAGES)[number]); return idx === -1 ? 99 : idx; } const CATEGORY_RANK: Record = { hot_lead: 0, specific_qualified: 1, general_interest: 2, }; const CATEGORY_LABELS: Record = { hot_lead: 'Hot lead', specific_qualified: 'Specific qualified', general_interest: 'General interest', }; interface ListResponse { data: InterestRow[]; total: number; } export function BerthInterestsTab({ berthId }: BerthInterestsTabProps) { const params = useParams<{ portSlug: string }>(); const portSlug = params?.portSlug ?? ''; const [stage, setStage] = useState('all'); const [sortMode, setSortMode] = useState('newest'); const { data, isLoading } = useQuery({ queryKey: ['interests', 'by-berth', berthId], queryFn: () => apiFetch(`/api/v1/interests?berthId=${berthId}&limit=200`), staleTime: 30_000, }); useRealtimeInvalidation({ 'interest:created': [['interests', 'by-berth', berthId]], 'interest:updated': [['interests', 'by-berth', berthId]], 'interest:stageChanged': [['interests', 'by-berth', berthId]], 'interest:archived': [['interests', 'by-berth', berthId]], 'interest:berthLinked': [['interests', 'by-berth', berthId]], 'interest:berthUnlinked': [['interests', 'by-berth', berthId]], }); const rows = useMemo(() => { const all = data?.data ?? []; const filtered = all.filter((i) => { // 2026-05-14 sentinel-stage cleanup: an interest is "active" when // it has no terminal outcome and isn't archived. The legacy // `pipelineStage !== 'completed'` check stopped working after the // 7-stage refactor stopped writing 'completed' for terminal rows. if (stage === 'active') return !i.outcome && !i.archivedAt; if (stage === 'lost') return Boolean(i.archivedAt); return true; }); const sorted = [...filtered].sort((a, b) => { if (sortMode === 'stage') { const sa = stageRank(a.pipelineStage); const sb = stageRank(b.pipelineStage); if (sa !== sb) return sb - sa; // furthest along first } if (sortMode === 'category') { const ca = CATEGORY_RANK[a.leadCategory ?? ''] ?? 99; const cb = CATEGORY_RANK[b.leadCategory ?? ''] ?? 99; if (ca !== cb) return ca - cb; // hottest first } // Default + tiebreaker: newest first. return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(); }); return sorted; }, [data?.data, stage, sortMode]); if (isLoading) return ; if ((data?.data ?? []).length === 0) { return ( ); } return (
{rows.length} of {data?.total ?? 0} interest{(data?.total ?? 0) === 1 ? '' : 's'}
{rows.map((i) => ( ))}
Client Stage Category Source Last activity Actions
{i.clientName ?? '-'} {stageLabel(i.pipelineStage)} {i.leadCategory ? (CATEGORY_LABELS[i.leadCategory] ?? i.leadCategory) : '-'} {formatSource(i.source) ?? '-'} {new Date(i.createdAt).toLocaleDateString()}
); }