'use client'; import { useState } from 'react'; import { useParams } from 'next/navigation'; import { Plus, LayoutList, Kanban, Archive } from 'lucide-react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { Button } from '@/components/ui/button'; import { DataTable } from '@/components/shared/data-table'; import { FilterBar } from '@/components/shared/filter-bar'; import { SavedViewsDropdown } from '@/components/shared/saved-views-dropdown'; import { PageHeader } from '@/components/shared/page-header'; import { EmptyState } from '@/components/shared/empty-state'; import { TableSkeleton } from '@/components/shared/loading-skeleton'; import { ArchiveConfirmDialog } from '@/components/shared/archive-confirm-dialog'; import { PermissionGate } from '@/components/shared/permission-gate'; import { InterestForm } from '@/components/interests/interest-form'; import { PipelineBoard } from '@/components/interests/pipeline-board'; import { interestFilterDefinitions } from '@/components/interests/interest-filters'; import { getInterestColumns, type InterestRow } from '@/components/interests/interest-columns'; import { InterestCard } from '@/components/interests/interest-card'; import { usePaginatedQuery } from '@/hooks/use-paginated-query'; import { useRealtimeInvalidation } from '@/hooks/use-realtime-invalidation'; import { apiFetch } from '@/lib/api/client'; import { usePipelineStore } from '@/stores/pipeline-store'; export function InterestList() { const params = useParams<{ portSlug: string }>(); const portSlug = params?.portSlug ?? ''; const queryClient = useQueryClient(); const { viewMode, setViewMode } = usePipelineStore(); const [createOpen, setCreateOpen] = useState(false); const [editInterest, setEditInterest] = useState(null); const [archiveInterest, setArchiveInterest] = useState(null); const { data, pagination, isLoading, isFetching, sort, setSort, setPage, setPageSize, filters, setFilter, clearFilters, } = usePaginatedQuery({ queryKey: ['interests'], endpoint: '/api/v1/interests', filterDefinitions: interestFilterDefinitions, }); useRealtimeInvalidation({ 'interest:created': [['interests']], 'interest:updated': [['interests']], 'interest:stageChanged': [['interests']], 'interest:archived': [['interests']], 'interest:berthLinked': [['interests']], 'interest:berthUnlinked': [['interests']], }); const archiveMutation = useMutation({ mutationFn: (id: string) => apiFetch(`/api/v1/interests/${id}`, { method: 'DELETE' }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['interests'] }); setArchiveInterest(null); }, }); const bulkArchiveMutation = useMutation({ mutationFn: async (ids: string[]) => { // Concurrent fan-out - small batches in practice (page size cap = 100). // If a single delete fails the others still run; the rejected one // surfaces a toast via the standard apiFetch error path. await Promise.all(ids.map((id) => apiFetch(`/api/v1/interests/${id}`, { method: 'DELETE' }))); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['interests'] }); }, }); const columns = getInterestColumns({ portSlug, onEdit: (interest) => setEditInterest(interest), onArchive: (interest) => setArchiveInterest(interest), }); return (
} />
{ clearFilters(); Object.entries(savedFilters).forEach(([key, val]) => setFilter(key, val)); }} />
{viewMode === 'board' ? ( ) : isLoading ? ( ) : ( { setPage(p); setPageSize(ps); }} sort={sort} onSortChange={setSort} isLoading={isFetching && !isLoading} getRowId={(row) => row.id} bulkActions={[ { label: 'Archive', icon: Archive, variant: 'destructive', onClick: (ids) => { if (ids.length === 0) return; if ( !window.confirm( `Archive ${ids.length} interest${ids.length === 1 ? '' : 's'}? This can be undone from the archived list.`, ) ) { return; } bulkArchiveMutation.mutate(ids); }, }, ]} cardRender={(row) => ( )} emptyState={ setCreateOpen(true) }} /> } /> )} {/* Mobile FAB - primary "New interest" affordance for the bottom-tab UX. Sits above the bottom nav (pb-safe-bottom + 70px tab height + 16px gap). Hidden on lg+ where the header button already does the job. */} {editInterest && ( !open && setEditInterest(null)} interest={editInterest as unknown as Parameters[0]['interest']} /> )} !open && setArchiveInterest(null)} entityName={archiveInterest?.clientName ?? 'Interest'} entityType="Interest" isArchived={false} onConfirm={() => archiveInterest && archiveMutation.mutate(archiveInterest.id)} isLoading={archiveMutation.isPending} /> ); }