mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
feat: Lead Master — column show/hide toggle, resizable table, remove dead filter/sort buttons
- ColumnToggle component with checkbox dropdown for column visibility - useColumnVisibility hook for state management - Campaign/Ad/FirstContact/Spam/Dups hidden by default (mostly empty) - ResizableTableContainer wrapping Table for column resize support - Column defaultWidth/minWidth props - Removed non-functional Filter and Sort buttons Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,7 @@ type LeadTableProps = {
|
||||
sortDirection: 'asc' | 'desc';
|
||||
onSort: (field: string) => void;
|
||||
onViewActivity?: (lead: Lead) => void;
|
||||
visibleColumns?: Set<string>;
|
||||
};
|
||||
|
||||
type TableRow = {
|
||||
@@ -53,6 +54,7 @@ export const LeadTable = ({
|
||||
sortDirection,
|
||||
onSort,
|
||||
onViewActivity,
|
||||
visibleColumns,
|
||||
}: LeadTableProps) => {
|
||||
const [expandedDupId, setExpandedDupId] = useState<string | null>(null);
|
||||
|
||||
@@ -92,22 +94,26 @@ export const LeadTable = ({
|
||||
return rows;
|
||||
}, [leads, expandedDupId]);
|
||||
|
||||
const columns = [
|
||||
{ id: 'phone', label: 'Phone', allowsSorting: true },
|
||||
{ id: 'name', label: 'Name', allowsSorting: true },
|
||||
{ id: 'email', label: 'Email', allowsSorting: false },
|
||||
{ id: 'campaign', label: 'Campaign', allowsSorting: false },
|
||||
{ id: 'ad', label: 'Ad', allowsSorting: false },
|
||||
{ id: 'source', label: 'Source', allowsSorting: true },
|
||||
{ id: 'firstContactedAt', label: 'First Contact', allowsSorting: true },
|
||||
{ id: 'lastContactedAt', label: 'Last Contact', allowsSorting: true },
|
||||
{ id: 'status', label: 'Status', allowsSorting: true },
|
||||
{ id: 'createdAt', label: 'Age', allowsSorting: true },
|
||||
{ id: 'spamScore', label: 'Spam', allowsSorting: true },
|
||||
{ id: 'dups', label: 'Dups', allowsSorting: false },
|
||||
{ id: 'actions', label: '', allowsSorting: false },
|
||||
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 (
|
||||
<div className="overflow-hidden rounded-xl ring-1 ring-secondary">
|
||||
<Table
|
||||
@@ -127,6 +133,9 @@ export const LeadTable = ({
|
||||
id={column.id}
|
||||
label={column.label}
|
||||
allowsSorting={column.allowsSorting}
|
||||
allowsResizing={column.id !== 'actions'}
|
||||
defaultWidth={column.defaultWidth}
|
||||
minWidth={50}
|
||||
/>
|
||||
)}
|
||||
</Table.Header>
|
||||
@@ -204,6 +213,8 @@ export const LeadTable = ({
|
||||
const isDup = lead.isDuplicate === true;
|
||||
const isExpanded = expandedDupId === lead.id;
|
||||
|
||||
const isCol = (id: string) => !visibleColumns || visibleColumns.has(id);
|
||||
|
||||
return (
|
||||
<Table.Row
|
||||
key={row.id}
|
||||
@@ -213,16 +224,16 @@ export const LeadTable = ({
|
||||
isSelected && 'bg-brand-primary',
|
||||
)}
|
||||
>
|
||||
<Table.Cell>
|
||||
{isCol('phone') && <Table.Cell>
|
||||
<span className="font-semibold text-primary">{phone}</span>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
</Table.Cell>}
|
||||
{isCol('name') && <Table.Cell>
|
||||
<span className="text-secondary">{name}</span>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
</Table.Cell>}
|
||||
{isCol('email') && <Table.Cell>
|
||||
<span className="text-tertiary">{email}</span>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
</Table.Cell>}
|
||||
{isCol('campaign') && <Table.Cell>
|
||||
{lead.utmCampaign ? (
|
||||
<Badge size="sm" type="pill-color" color="purple">
|
||||
{lead.utmCampaign}
|
||||
@@ -230,8 +241,8 @@ export const LeadTable = ({
|
||||
) : (
|
||||
<span className="text-tertiary">{'\u2014'}</span>
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
</Table.Cell>}
|
||||
{isCol('ad') && <Table.Cell>
|
||||
{lead.adId ? (
|
||||
<Badge size="sm" type="pill-color" color="success">
|
||||
Ad
|
||||
@@ -239,50 +250,50 @@ export const LeadTable = ({
|
||||
) : (
|
||||
<span className="text-tertiary">{'\u2014'}</span>
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
</Table.Cell>}
|
||||
{isCol('source') && <Table.Cell>
|
||||
{lead.leadSource ? (
|
||||
<SourceTag source={lead.leadSource} />
|
||||
) : (
|
||||
<span className="text-tertiary">{'\u2014'}</span>
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
</Table.Cell>}
|
||||
{isCol('firstContactedAt') && <Table.Cell>
|
||||
<span className="text-tertiary">
|
||||
{lead.firstContactedAt
|
||||
? formatShortDate(lead.firstContactedAt)
|
||||
: '\u2014'}
|
||||
</span>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
</Table.Cell>}
|
||||
{isCol('lastContactedAt') && <Table.Cell>
|
||||
<span className="text-tertiary">
|
||||
{lead.lastContactedAt
|
||||
? formatShortDate(lead.lastContactedAt)
|
||||
: '\u2014'}
|
||||
</span>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
</Table.Cell>}
|
||||
{isCol('status') && <Table.Cell>
|
||||
{lead.leadStatus ? (
|
||||
<LeadStatusBadge status={lead.leadStatus} />
|
||||
) : (
|
||||
<span className="text-tertiary">{'\u2014'}</span>
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
</Table.Cell>}
|
||||
{isCol('createdAt') && <Table.Cell>
|
||||
{lead.createdAt ? (
|
||||
<AgeIndicator dateStr={lead.createdAt} />
|
||||
) : (
|
||||
<span className="text-tertiary">{'\u2014'}</span>
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
</Table.Cell>}
|
||||
{isCol('spamScore') && <Table.Cell>
|
||||
{lead.spamScore != null ? (
|
||||
<SpamDisplay score={lead.spamScore} />
|
||||
) : (
|
||||
<span className="text-tertiary">0%</span>
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
</Table.Cell>}
|
||||
{isCol('dups') && <Table.Cell>
|
||||
{isDup ? (
|
||||
<button
|
||||
type="button"
|
||||
@@ -297,7 +308,7 @@ export const LeadTable = ({
|
||||
) : (
|
||||
<span className="text-tertiary">0</span>
|
||||
)}
|
||||
</Table.Cell>
|
||||
</Table.Cell>}
|
||||
<Table.Cell>
|
||||
<Button
|
||||
size="sm"
|
||||
|
||||
Reference in New Issue
Block a user