Files
plantillas-proyectos/frontend/src/lib/components/dashboard/export/manifest/data-table.svelte

137 lines
3.5 KiB
Svelte

<script lang="ts" generics="TData, TValue">
import { onMount } from 'svelte';
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;
hasMore?: boolean;
loadMore?: () => void;
// Props para selección
selectedId?: number | null;
onRowClick?: (row: TData) => void;
};
let {
data,
columns,
loading = false,
hasMore = false,
loadMore,
selectedId = null,
onRowClick
}: DataTableProps<TData, TValue> = $props();
const table = createSvelteTable({
get data() {
return data;
},
columns,
getCoreRowModel: getCoreRowModel(),
getRowId: (row: any) => row.id?.toString(),
state: {
get rowSelection() {
return selectedId ? { [selectedId]: true } : {};
}
},
enableRowSelection: true,
enableMultiRowSelection: false
});
let scrollContainer = $state<HTMLDivElement>();
let loadingTrigger = $state<HTMLDivElement>();
onMount(() => {
if (!loadMore) return;
const observer = new IntersectionObserver(
(entries) => {
const [entry] = entries;
if (entry.isIntersecting && hasMore && !loading) {
loadMore();
}
},
{
root: null, // Relative to viewport if scrollContainer is used as max-h div
threshold: 0.1
}
);
if (loadingTrigger) {
observer.observe(loadingTrigger);
}
return () => {
observer.disconnect();
};
});
</script>
<div class="w-full">
<div class="max-h-[600px] overflow-y-auto rounded-md border" bind:this={scrollContainer}>
<Table.Root>
<Table.Header class="sticky top-0 z-10 bg-background">
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
{#each headerGroup.headers as header (header.id)}
<Table.Head>
{#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
data-state={row.getIsSelected() && 'selected'}
class="cursor-pointer transition-colors {row.getIsSelected()
? 'bg-muted'
: 'hover:bg-muted/50'}"
onclick={() => onRowClick && onRowClick(row.original)}
>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell>
<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">
No hay resultados.
</Table.Cell>
</Table.Row>
{/each}
{#if hasMore}
<Table.Row>
<Table.Cell colspan={columns.length} class="h-20 text-center">
<div bind:this={loadingTrigger}>
{#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>
<span class="text-sm text-muted-foreground">Cargando más...</span>
</div>
{:else}
<div class="text-sm text-muted-foreground">Desplázate para cargar más</div>
{/if}
</div>
</Table.Cell>
</Table.Row>
{/if}
</Table.Body>
</Table.Root>
</div>
</div>