Files
plantillas-proyectos/frontend/src/lib/components/dashboard/goods/classes/data-table.svelte
2026-04-21 11:23:35 -06:00

192 lines
5.8 KiB
Svelte

<script lang="ts" generics="TData, TValue">
import {
type ColumnDef,
getCoreRowModel
} from "@tanstack/table-core";
import { createSvelteTable, FlexRender } from "$lib/components/ui/data-table/index.js";
import * as Table from "$lib/components/ui/table/index.js";
type DataTableProps<TData, TValue> = {
columns: ColumnDef<TData, TValue>[];
data: TData[];
loading: boolean;
selectedIds?: number[];
onSelectedIdsChange?: (selectedIds: number[]) => void;
onRowDoubleClick?: (row: TData) => void;
sorting?: import("@tanstack/table-core").SortingState;
onSortingChange?: (sorting: import("@tanstack/table-core").SortingState) => void;
};
let {
data,
columns,
loading,
selectedIds = [],
onSelectedIdsChange,
onRowDoubleClick,
sorting = [],
onSortingChange
}: DataTableProps<TData, TValue> = $props();
function getRowIdValue(row: TData): number | null {
const candidate = (row as { id?: unknown })?.id;
return typeof candidate === 'number' ? candidate : null;
}
function toggleSelectedId(rowId: number, forceSelected?: boolean) {
const isSelected = selectedIds.includes(rowId);
const shouldSelect = forceSelected ?? !isSelected;
if (shouldSelect && !isSelected) {
onSelectedIdsChange?.([...selectedIds, rowId]);
return;
}
if (!shouldSelect && isSelected) {
onSelectedIdsChange?.(selectedIds.filter((id) => id !== rowId));
}
}
const table = createSvelteTable({
get data() {
return data;
},
columns,
getCoreRowModel: getCoreRowModel(),
getRowId: (row: any) => row.id?.toString(),
state: {
get rowSelection() {
return Object.fromEntries(selectedIds.map((id) => [id.toString(), true]));
},
get sorting() {
return sorting;
}
},
meta: {
toggleSelectedId
},
onSortingChange: (updater) => {
if (onSortingChange) {
const nextSorting = typeof updater === 'function' ? updater(sorting) : updater;
onSortingChange(nextSorting);
}
},
manualSorting: true,
enableRowSelection: true,
enableMultiRowSelection: true
});
</script>
<div class="w-full h-full flex flex-col overflow-hidden">
<div class="rounded-md border flex-1 overflow-auto bg-white dark:bg-black">
<Table.Root class="w-full">
<Table.Header class="bg-background sticky top-0 z-10">
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
{#each headerGroup.headers as header (header.id)}
<Table.Head class="whitespace-nowrap p-0">
{#if !header.isPlaceholder}
<button
class="group flex h-full w-full items-center gap-2 px-2 py-2 text-left hover:bg-muted/50"
onclick={header.column.getToggleSortingHandler()}
disabled={!header.column.getCanSort()}
>
<FlexRender
content={header.column.columnDef.header}
context={header.getContext()}
/>
{#if header.column.getCanSort()}
<div class="flex h-4 w-4 shrink-0 items-center justify-center">
{#if header.column.getIsSorted() === 'asc'}
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-chevron-up text-primary"
><path d="m18 15-6-6-6 6" /></svg
>
{:else if header.column.getIsSorted() === 'desc'}
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-chevron-down text-primary"
><path d="m6 9 6 6 6-6" /></svg
>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-chevrons-up-down opacity-30 group-hover:opacity-100"
><path d="m7 15 5 5 5-5" /><path d="m7 9 5-5 5 5" /></svg
>
{/if}
</div>
{/if}
</button>
{/if}
</Table.Head>
{/each}
</Table.Row>
{/each}
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row
data-state={row.getIsSelected() && "selected"}
onclick={() => {
const rowId = getRowIdValue(row.original);
if (rowId !== null) {
toggleSelectedId(rowId);
}
}}
ondblclick={() => onRowDoubleClick?.(row.original)}
class="cursor-pointer hover:bg-muted/50 transition-colors {row.getIsSelected() ? 'bg-primary/10' : ''}"
>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell class="whitespace-nowrap px-2 py-1">
<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">
{#if loading}
<div class="flex items-center justify-center gap-2">
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
<p class="text-sm text-muted-foreground">Cargando...</p>
</div>
{:else}
No hay resultados.
{/if}
</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
</div>
</div>