blanchon's picture
Match table: prepend match_id column, truncate event; add stats section
0f00bbc
<script lang="ts" generics="TData">
import {
type ColumnDef,
type ColumnFiltersState,
type PaginationState,
type SortingState,
type VisibilityState,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel
} from '@tanstack/table-core';
import { createSvelteTable, FlexRender } from '$lib/components/ui/data-table';
import * as Table from '$lib/components/ui/table';
import DataTableToolbar from './data-table-toolbar.svelte';
import DataTablePagination from './data-table-pagination.svelte';
import { goto } from '$app/navigation';
import { cn } from '$lib/utils';
import { untrack } from 'svelte';
interface Props {
data: TData[];
columns: ColumnDef<TData>[];
searchPlaceholder?: string;
mapOptions?: string[];
getRowHref?: (row: TData) => string;
emptyMessage?: string;
// Bindable state so the parent route can persist it across navigations
// via SvelteKit's snapshot API.
pagination?: PaginationState;
sorting?: SortingState;
columnFilters?: ColumnFiltersState;
columnVisibility?: VisibilityState;
globalFilter?: string;
}
let {
data,
columns,
searchPlaceholder,
mapOptions = [],
getRowHref,
emptyMessage = 'No results.',
pagination = $bindable<PaginationState>({ pageIndex: 0, pageSize: 25 }),
sorting = $bindable<SortingState>([]),
columnFilters = $bindable<ColumnFiltersState>([]),
columnVisibility = $bindable<VisibilityState>({}),
globalFilter = $bindable<string>('')
}: Props = $props();
// Reset to first page whenever filters change so users don't end up on an
// empty page after narrowing the result set.
$effect(() => {
void globalFilter;
void columnFilters;
untrack(() => {
if (pagination.pageIndex !== 0) {
pagination = { ...pagination, pageIndex: 0 };
}
});
});
const table = createSvelteTable<TData>({
get data() {
return data;
},
get columns() {
return columns;
},
state: {
get pagination() {
return pagination;
},
get sorting() {
return sorting;
},
get columnFilters() {
return columnFilters;
},
get columnVisibility() {
return columnVisibility;
},
get globalFilter() {
return globalFilter;
}
},
globalFilterFn: 'includesString',
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onPaginationChange: (u) => {
pagination = typeof u === 'function' ? u(pagination) : u;
},
onSortingChange: (u) => {
sorting = typeof u === 'function' ? u(sorting) : u;
},
onColumnFiltersChange: (u) => {
columnFilters = typeof u === 'function' ? u(columnFilters) : u;
},
onColumnVisibilityChange: (u) => {
columnVisibility = typeof u === 'function' ? u(columnVisibility) : u;
},
onGlobalFilterChange: (u) => {
globalFilter = typeof u === 'function' ? u(globalFilter) : u;
}
});
function handleRowClick(row: TData, e: MouseEvent | KeyboardEvent) {
if (!getRowHref) return;
const href = getRowHref(row);
if (e instanceof MouseEvent && (e.metaKey || e.ctrlKey || e.button === 1)) {
window.open(href, '_blank', 'noopener,noreferrer');
return;
}
goto(href);
}
</script>
<div>
<DataTableToolbar {table} {searchPlaceholder} {mapOptions} />
<div class="rounded-md border">
<Table.Root>
<Table.Header>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row class="bg-muted/40 hover:bg-muted/40">
{#each headerGroup.headers as header (header.id)}
<Table.Head class={cn('h-9 text-xs', header.column.columnDef.meta?.headClass)}>
{#if !header.isPlaceholder}
<FlexRender
content={header.column.columnDef.header}
context={header.getContext()}
/>
{/if}
</Table.Head>
{/each}
</Table.Row>
{/each}
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row
class={cn(getRowHref && 'cursor-pointer')}
onclick={(e: MouseEvent) => handleRowClick(row.original, e)}
onkeydown={(e: KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleRowClick(row.original, e);
}
}}
tabindex={getRowHref ? 0 : undefined}
role={getRowHref ? 'link' : undefined}
>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell class={cn('py-2 align-middle', cell.column.columnDef.meta?.cellClass)}>
<FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} />
</Table.Cell>
{/each}
</Table.Row>
{:else}
<Table.Row>
<Table.Cell
colspan={columns.length}
class="h-24 text-center text-sm text-muted-foreground"
>
{emptyMessage}
</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
</div>
<DataTablePagination {table} />
</div>