Leads Table
A contractor leads management table showing customer alerts, equipment status, and breakdown risk assessments.
A contractor leads management table showing customer alerts, equipment status, and breakdown risk assessments.
| Customer Name | Summary | Equipment | Urgency | ||
|---|---|---|---|---|---|
| Luke Klapesky | New | Compressor showing pre-failure patterns | Goodman GSX140361 | 24 hrs | |
| Sarah Mitchell | Routed | Unusual energy consumption spike detected | Carrier 24ACC636 | 3-5 days | |
| Michael Chen | Scheduled | Annual maintenance requested | Trane XR15-048 | 1 week | |
| Emily Rodriguez | New | Refrigerant leak detected via pressure drop | Lennox XC21-036 | 48 hrs | |
| James Wilson | Scheduled | Compressor failure imminent | Rheem RA1436AJ | 24 hrs | |
| Amanda Foster | New | Homeowner reports inconsistent temperatures | Goodman GSX130301 | 2-3 days | |
| David Park | Scheduled | Airflow restriction pattern detected | Carrier 25HPA936 | 5-7 days | |
| Jennifer Adams | Completed | Minor efficiency degradation observed | Trane XR13-036 | 2 weeks | |
| Robert Martinez | Routed | Capacitor showing degradation pattern | Lennox XC14-036 | 24 hrs | |
| Lisa Thompson | New | Poor cooling delta detected | Mitsubishi MSZ-FH12 | 3-5 days |
"use client";
import * as React from "react";
import {
ColumnDef,
ColumnFiltersState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
Table as TTable,
useReactTable,
VisibilityState,
} from "@tanstack/react-table";
import { Badge } from "@smartacteam/ambient-web/badge";
import { Button } from "@smartacteam/ambient-web/button";
import { Checkbox } from "@smartacteam/ambient-web/checkbox";
import { Menu } from "@smartacteam/ambient-web/dropdown-menu";
import { CentralIcon } from "@smartacteam/ambient-web/icon";
import { Table } from "@smartacteam/ambient-web/table";
import { Tooltip } from "@smartacteam/ambient-web/tooltip";
import {
Lead,
LeadAffectedSystem,
leads,
LeadStatus,
LeadUrgencyLevel,
LeadValueTier,
} from "./data/leads-data";
type StatusInfo = {
label: string;
icon: string;
color: "default" | "success" | "warning";
};
function getStatusInfo(status: LeadStatus): StatusInfo {
switch (status) {
case "new":
return { label: "New", icon: "IconCirclePlus", color: "default" };
case "routed":
return { label: "Routed", icon: "IconArrowRight", color: "warning" };
case "scheduled":
return {
label: "Scheduled",
icon: "IconCalendarCheck",
color: "default",
};
case "completed":
return { label: "Completed", icon: "IconCircleCheck", color: "success" };
case "revenue":
return { label: "Revenue", icon: "IconCurrencyDollar", color: "success" };
}
}
function getAffectedSystemIcon(system: LeadAffectedSystem) {
switch (system) {
case "cooling":
return "IconSnowFlakes";
case "heating":
return "IconFire3";
case "leak":
return "IconDrop";
}
}
function getValueTierLabel(tier: LeadValueTier) {
switch (tier) {
case "high":
return "High";
case "medium":
return "Medium";
case "low":
return "Low";
}
}
function getUrgencyColor(urgency: LeadUrgencyLevel) {
switch (urgency) {
case "immediate":
return "error";
case "soon":
return "warning";
case "flexible":
return "default";
}
}
export const columns: ColumnDef<Lead>[] = [
{
id: "select",
header: ({ table }) => (
<Checkbox
checked={table.getIsAllPageRowsSelected()}
indeterminate={table.getIsSomePageRowsSelected()}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
),
enableSorting: false,
enableHiding: false,
},
{
id: "customer",
accessorKey: "customerName",
header: "Customer Name",
},
{
accessorKey: "status",
header: "",
cell: ({ row }) => {
const statusInfo = getStatusInfo(row.original.status);
return (
<Badge color={statusInfo.color} size="sm">
<CentralIcon
name={statusInfo.icon as "IconCircleCheck"}
radius="2"
stroke="1.5"
fill="outlined"
className="size-3"
/>
{statusInfo.label}
</Badge>
);
},
},
{
accessorKey: "summary",
header: "Summary",
cell: ({ row }) => {
const lead = row.original;
return (
<Tooltip.Root>
<Tooltip.Trigger
render={
<div className="flex max-w-xs cursor-default items-center gap-2">
<CentralIcon
name={
getAffectedSystemIcon(
lead.affectedSystem,
) as "IconSnowFlakes"
}
radius="2"
stroke="1.5"
fill="outlined"
className="size-4 shrink-0 text-foreground-secondary"
/>
<span className="truncate">{lead.summary}</span>
</div>
}
/>
<Tooltip.Content side="bottom" align="start">
<div className="space-y-1 text-sm">
<div className="font-medium">{lead.summary}</div>
<div className="text-foreground-secondary">
Value: {getValueTierLabel(lead.valueTier)}
{lead.estimatedValue && ` (~$${lead.estimatedValue})`}
</div>
<div className="text-foreground-secondary">
Confidence: {lead.confidence}%
</div>
</div>
</Tooltip.Content>
</Tooltip.Root>
);
},
},
{
accessorKey: "equipment",
header: "Equipment",
cell: ({ row }) => {
const lead = row.original;
return (
<Tooltip.Root>
<Tooltip.Trigger
render={
<span className="cursor-default">
{lead.equipment.manufacturer} {lead.equipment.unitNumber}
</span>
}
/>
<Tooltip.Content side="bottom" align="start">
<div className="space-y-1 text-sm">
<div className="font-medium">
{lead.equipment.manufacturer} {lead.equipment.unitNumber}
</div>
<div className="text-app-color-foreground-secondary">
{lead.equipment.systemType} · {lead.equipment.age} years old
</div>
<div className="text-app-color-foreground-secondary">
Refrigerant: {lead.equipment.refrigerant}
</div>
</div>
</Tooltip.Content>
</Tooltip.Root>
);
},
},
{
accessorKey: "urgency",
header: "Urgency",
cell: ({ row }) => {
const lead = row.original;
return (
<Badge color={getUrgencyColor(lead.urgency)} size="sm">
{lead.urgencyWindow}
</Badge>
);
},
},
{
id: "leadType",
accessorKey: "leadType",
header: "Type",
enableHiding: true,
},
{
id: "valueTier",
accessorKey: "valueTier",
header: "Value",
enableHiding: true,
},
{
id: "origin",
accessorKey: "origin",
header: "Origin",
enableHiding: true,
},
];
function FilterChip({
label,
options,
table,
filterKey,
}: {
label: string;
filterKey: string;
table: TTable<Lead>;
options: { value: string; label: string }[];
}) {
return (
<Menu.Root>
<Menu.Trigger
render={
<Button size="sm">
<span className="text-foreground-secondary">{label}:</span>
<span>
{options.find(
(o) => o.value === table.getColumn(filterKey)?.getFilterValue(),
)?.label ?? "All"}
</span>
<CentralIcon
name="IconChevronDownMedium"
radius="2"
stroke="1.5"
fill="outlined"
className="size-3.5 text-foreground-secondary"
/>
</Button>
}
/>
<Menu.Content align="start">
{options.map((option) => (
<Menu.CheckboxItem
key={option.value}
checked={
table.getColumn(filterKey)?.getFilterValue() === option.value
}
onCheckedChange={() => {
if (option.value === "all") {
return table.getColumn(filterKey)?.setFilterValue(undefined);
}
table.getColumn(filterKey)?.setFilterValue(option.value);
}}
>
{option.label}
</Menu.CheckboxItem>
))}
</Menu.Content>
</Menu.Root>
);
}
export function LeadTableDemo() {
const [rowSelection, setRowSelection] = React.useState({});
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[],
);
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({
leadType: false,
valueTier: false,
origin: false,
});
const table = useReactTable({
data: leads,
columns,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
state: {
columnFilters,
columnVisibility,
rowSelection,
},
});
return (
<div className="w-full">
<div className="flex items-center py-4">
<div className="flex w-full gap-component-sm">
<FilterChip
label="Type"
table={table}
filterKey="leadType"
options={[
{ value: "all", label: "All" },
{ value: "predictive", label: "Predictive" },
{ value: "anomaly", label: "Anomaly" },
{ value: "homeowner_reported", label: "Reported" },
{ value: "homeowner_scheduled", label: "Scheduled" },
{ value: "offline", label: "Offline" },
]}
/>
<FilterChip
label="Value"
table={table}
filterKey="valueTier"
options={[
{ value: "all", label: "All" },
{ value: "high", label: "High" },
{ value: "medium", label: "Medium" },
{ value: "low", label: "Low" },
]}
/>
<FilterChip
label="Urgency"
filterKey="urgency"
table={table}
options={[
{ value: "all", label: "All" },
{ value: "immediate", label: "Immediate" },
{ value: "soon", label: "Soon" },
{ value: "flexible", label: "Flexible" },
]}
/>
<FilterChip
label="Origin"
filterKey="origin"
table={table}
options={[
{ value: "all", label: "All" },
{ value: "smartac", label: "SmartAC" },
{ value: "homeowner", label: "Homeowner" },
]}
/>
<FilterChip
label="Status"
filterKey="status"
table={table}
options={[
{ value: "all", label: "All" },
{ value: "new", label: "New" },
{ value: "routed", label: "Routed" },
{ value: "scheduled", label: "Scheduled" },
{ value: "completed", label: "Completed" },
{ value: "revenue", label: "Revenue" },
]}
/>
</div>
<Menu.Root>
<Menu.Trigger
render={
<Button size="sm" className="ml-auto">
Columns{" "}
<CentralIcon
name="IconChevronDownMedium"
radius="2"
stroke="1.5"
fill="outlined"
className="size-3.5"
/>
</Button>
}
/>
<Menu.Content align="end">
{table
.getAllColumns()
.filter(
(column) =>
column.getCanHide() &&
!["leadType", "origin", "leadValueTier"].includes(column.id),
)
.map((column) => {
return (
<Menu.CheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{column.id}
</Menu.CheckboxItem>
);
})}
</Menu.Content>
</Menu.Root>
</div>
<Table.Root>
<Table.Header>
{table.getHeaderGroups().map((headerGroup) => (
<Table.Row key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<Table.Head key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</Table.Head>
);
})}
</Table.Row>
))}
</Table.Header>
<Table.Body>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<Table.Row
key={row.id}
interactive
className="group"
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => {
return (
<Table.Cell
key={cell.id}
variant={
cell.column.id === "customer" ? "header" : "default"
}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</Table.Cell>
);
})}
</Table.Row>
))
) : (
<Table.Row>
<Table.Cell colSpan={columns.length} className="h-24 text-center">
No results.
</Table.Cell>
</Table.Row>
)}
</Table.Body>
</Table.Root>
<div className="flex items-center justify-end space-x-2 py-4">
<div className="flex-1 text-sm text-foreground-secondary">
{table.getFilteredSelectedRowModel().rows.length} of{" "}
{table.getFilteredRowModel().rows.length} row(s) selected.
</div>
<div className="flex gap-2">
<Button
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
</div>
);
}