import type { FC } from 'react'; import { useMemo, useState } from 'react'; import { TableBody as AriaTableBody } from 'react-aria-components'; import type { SortDescriptor, Selection } from 'react-aria-components'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faEllipsisVertical } from '@fortawesome/pro-duotone-svg-icons'; const DotsVertical: FC<{ className?: string }> = ({ className }) => ; import { Badge } from '@/components/base/badges/badges'; import { Button } from '@/components/base/buttons/button'; import { Table } from '@/components/application/table/table'; import { LeadStatusBadge } from '@/components/shared/status-badge'; import { SourceTag } from '@/components/shared/source-tag'; import { AgeIndicator } from '@/components/shared/age-indicator'; import { formatPhone, formatShortDate } from '@/lib/format'; import { cx } from '@/utils/cx'; import type { Lead } from '@/types/entities'; type LeadTableProps = { leads: Lead[]; onSelectionChange: (selectedIds: string[]) => void; selectedIds: string[]; sortField: string; sortDirection: 'asc' | 'desc'; onSort: (field: string) => void; onViewActivity?: (lead: Lead) => void; visibleColumns?: Set; }; type TableRow = { id: string; type: 'lead' | 'dup-sub'; lead: Lead; }; const SpamDisplay = ({ score }: { score: number }) => { const colorClass = score < 30 ? 'text-success-primary' : score < 60 ? 'text-warning-primary' : 'text-error-primary'; const bgClass = score >= 60 ? 'rounded px-1.5 py-0.5 bg-error-primary' : ''; return {score}%; }; export const LeadTable = ({ leads, onSelectionChange, selectedIds, sortField, sortDirection, onSort, onViewActivity, visibleColumns, }: LeadTableProps) => { const [expandedDupId, setExpandedDupId] = useState(null); const selectedKeys: Selection = new Set(selectedIds); const handleSelectionChange = (keys: Selection) => { if (keys === 'all') { // Only select actual lead rows, not dup sub-rows onSelectionChange(leads.map((l) => l.id)); } else { // Filter out dup sub-row IDs const leadOnlyIds = [...keys].filter((k) => !String(k).endsWith('-dup')) as string[]; onSelectionChange(leadOnlyIds); } }; const sortDescriptor: SortDescriptor = { column: sortField, direction: sortDirection === 'asc' ? 'ascending' : 'descending', }; const handleSortChange = (descriptor: SortDescriptor) => { if (descriptor.column) { onSort(String(descriptor.column)); } }; // Flatten leads + expanded dup sub-rows into a single list const tableRows = useMemo(() => { const rows: TableRow[] = []; for (const lead of leads) { rows.push({ id: lead.id, type: 'lead', lead }); if (lead.isDuplicate === true && expandedDupId === lead.id) { rows.push({ id: `${lead.id}-dup`, type: 'dup-sub', lead }); } } return rows; }, [leads, expandedDupId]); const allColumns = [ { id: 'phone', label: 'Phone', allowsSorting: true, defaultWidth: 150 }, { id: 'name', label: 'Name', allowsSorting: true, defaultWidth: 160 }, { id: 'email', label: 'Email', allowsSorting: false, defaultWidth: 180 }, { id: 'campaign', label: 'Campaign', allowsSorting: false, defaultWidth: 140 }, { id: 'ad', label: 'Ad', allowsSorting: false, defaultWidth: 80 }, { id: 'source', label: 'Source', allowsSorting: true, defaultWidth: 100 }, { id: 'firstContactedAt', label: 'First Contact', allowsSorting: true, defaultWidth: 130 }, { id: 'lastContactedAt', label: 'Last Contact', allowsSorting: true, defaultWidth: 130 }, { id: 'status', label: 'Status', allowsSorting: true, defaultWidth: 100 }, { id: 'createdAt', label: 'Age', allowsSorting: true, defaultWidth: 80 }, { id: 'spamScore', label: 'Spam', allowsSorting: true, defaultWidth: 70 }, { id: 'dups', label: 'Dups', allowsSorting: false, defaultWidth: 60 }, { id: 'actions', label: '', allowsSorting: false, defaultWidth: 50 }, ]; const columns = visibleColumns ? allColumns.filter(c => visibleColumns.has(c.id) || c.id === 'actions') : allColumns; return (
{(column) => ( )} {(row) => { const { lead } = row; const firstName = lead.contactName?.firstName ?? ''; const lastName = lead.contactName?.lastName ?? ''; const name = `${firstName} ${lastName}`.trim() || '\u2014'; const phone = lead.contactPhone?.[0] ? formatPhone(lead.contactPhone[0]) : '\u2014'; const email = lead.contactEmail?.[0]?.address ?? '\u2014'; // Render duplicate sub-row if (row.type === 'dup-sub') { return ( {phone} {name} {email} {lead.leadSource ? ( ) : ( {'\u2014'} )} Same phone number {lead.createdAt ? formatShortDate(lead.createdAt) : '\u2014'}
); } // Render normal lead row const isSpamRow = (lead.spamScore ?? 0) >= 60; const isSelected = selectedIds.includes(lead.id); const isDup = lead.isDuplicate === true; const isExpanded = expandedDupId === lead.id; const isCol = (id: string) => !visibleColumns || visibleColumns.has(id); return ( {isCol('phone') && {phone} } {isCol('name') && {name} } {isCol('email') && {email} } {isCol('campaign') && {lead.utmCampaign ? ( {lead.utmCampaign} ) : ( {'\u2014'} )} } {isCol('ad') && {lead.adId ? ( Ad ) : ( {'\u2014'} )} } {isCol('source') && {lead.leadSource ? ( ) : ( {'\u2014'} )} } {isCol('firstContactedAt') && {lead.firstContactedAt ? formatShortDate(lead.firstContactedAt) : '\u2014'} } {isCol('lastContactedAt') && {lead.lastContactedAt ? formatShortDate(lead.lastContactedAt) : '\u2014'} } {isCol('status') && {lead.leadStatus ? ( ) : ( {'\u2014'} )} } {isCol('createdAt') && {lead.createdAt ? ( ) : ( {'\u2014'} )} } {isCol('spamScore') && {lead.spamScore != null ? ( ) : ( 0% )} } {isCol('dups') && {isDup ? ( ) : ( 0 )} }
); };