Build the agent-native compute CRM platform
CI / verify (push) Successful in 3m6s

This commit is contained in:
2026-08-13 01:39:01 -07:00
parent bfd2f8d95a
commit 853bde2265
160 changed files with 61812 additions and 483 deletions
+243
View File
@@ -0,0 +1,243 @@
import { useState } from 'react';
import {
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
type Column,
type ColumnDef,
type ColumnFiltersState,
type SortingState,
type VisibilityState,
} from '@tanstack/react-table';
import { ArrowDown, ArrowUp, ArrowUpDown, ChevronLeft, ChevronRight, SlidersHorizontal } from 'lucide-react';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Input } from '@/components/ui';
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
emptyMessage?: string;
filterColumn?: string;
filterPlaceholder?: string;
initialColumnVisibility?: VisibilityState;
}
export function DataTable<TData, TValue>({
columns,
data,
emptyMessage = 'No results.',
filterColumn,
filterPlaceholder = 'Filter results',
initialColumnVisibility = {},
}: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = useState<SortingState>([]);
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>(
initialColumnVisibility,
);
const table = useReactTable({
data,
columns,
state: { sorting, columnFilters, columnVisibility },
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onColumnVisibilityChange: setColumnVisibility,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
});
const activeFilter = filterColumn ? table.getColumn(filterColumn) : undefined;
const hideableColumns = table.getAllColumns().filter((column) => column.getCanHide());
return (
<div className="flex flex-col gap-3">
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
{activeFilter ? (
<Input
type="search"
value={(activeFilter.getFilterValue() as string | undefined) ?? ''}
onChange={(event) => activeFilter.setFilterValue(event.target.value)}
placeholder={filterPlaceholder}
aria-label={filterPlaceholder}
className="sm:max-w-xs"
/>
) : (
<span />
)}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="tap sm:ml-auto">
<SlidersHorizontal data-icon="inline-start" aria-hidden />
Columns
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Visible columns</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
{hideableColumns.map((column) => (
<DropdownMenuCheckboxItem
key={column.id}
checked={column.getIsVisible()}
onCheckedChange={(visible) => column.toggleVisibility(Boolean(visible))}
>
{columnLabel(column.id)}
</DropdownMenuCheckboxItem>
))}
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="scroll-x rounded-xl border border-border bg-surface">
<Table className="min-w-[44rem]">
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows.length ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id} data-state={row.getIsSelected() ? 'selected' : undefined}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={Math.max(table.getVisibleLeafColumns().length, 1)} className="h-28 text-center text-muted">
{emptyMessage}
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<p className="text-sm text-muted" aria-live="polite">
{table.getFilteredRowModel().rows.length} result
{table.getFilteredRowModel().rows.length === 1 ? '' : 's'} · Page{' '}
{table.getState().pagination.pageIndex + 1} of {Math.max(table.getPageCount(), 1)}
</p>
<div className="flex items-center justify-between gap-2 sm:justify-end">
<Select
value={String(table.getState().pagination.pageSize)}
onValueChange={(value) => table.setPageSize(Number(value))}
>
<SelectTrigger className="h-11 w-[7.5rem]" aria-label="Rows per page">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{[10, 20, 50].map((pageSize) => (
<SelectItem key={pageSize} value={String(pageSize)}>
{pageSize} rows
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<Button
type="button"
variant="outline"
size="icon"
className="tap"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
aria-label="Previous page"
>
<ChevronLeft aria-hidden />
</Button>
<Button
type="button"
variant="outline"
size="icon"
className="tap"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
aria-label="Next page"
>
<ChevronRight aria-hidden />
</Button>
</div>
</div>
</div>
);
}
export function DataTableColumnHeader<TData, TValue>({
column,
title,
}: {
column: Column<TData, TValue>;
title: string;
}) {
if (!column.getCanSort()) return <span>{title}</span>;
const direction = column.getIsSorted();
const SortIcon = direction === 'asc' ? ArrowUp : direction === 'desc' ? ArrowDown : ArrowUpDown;
return (
<Button
type="button"
variant="ghost"
size="sm"
className="-ml-3"
onClick={() => column.toggleSorting(direction === 'asc')}
aria-label={`Sort by ${title}${direction ? `, currently ${direction}ending` : ''}`}
>
{title}
<SortIcon data-icon="inline-end" aria-hidden />
</Button>
);
}
function columnLabel(value: string): string {
return value
.replace(/([a-z])([A-Z])/g, '$1 $2')
.replace(/_/g, ' ')
.replace(/^./, (character) => character.toUpperCase());
}