feat: Implement token refresh mechanism and infinite scroll for code pedimento regimens

This commit is contained in:
2025-11-02 13:48:42 -06:00
parent 2b735c27b8
commit 886a3aeab3
6 changed files with 449 additions and 112 deletions

View File

@@ -1,78 +1,68 @@
<script lang="ts" generics="TData, TValue">
import { onMount } from 'svelte';
import {
type ColumnDef,
type PaginationState,
getCoreRowModel,
getPaginationRowModel
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";
import { Button } from "$lib/components/ui/button/index.js";
type DataTableProps<TData, TValue> = {
columns: ColumnDef<TData, TValue>[];
data: TData[];
totalItems: number;
currentPage: number;
pageSize: number;
onPageChange: (page: number) => void;
loading: boolean;
hasMore: boolean;
loadMore: () => void;
};
let {
data,
columns,
totalItems,
currentPage,
pageSize,
onPageChange
loading,
hasMore,
loadMore
}: DataTableProps<TData, TValue> = $props();
let pagination = $state<PaginationState>({
pageIndex: currentPage - 1,
pageSize: pageSize
});
const table = createSvelteTable({
get data() {
return data;
},
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
manualPagination: true,
pageCount: Math.ceil(totalItems / pageSize),
state: {
get pagination() {
return pagination;
}
}
getCoreRowModel: getCoreRowModel()
});
$effect(() => {
pagination = {
pageIndex: currentPage - 1,
pageSize: pageSize
let scrollContainer = $state<HTMLDivElement>();
let loadingTrigger = $state<HTMLDivElement>();
// Intersection Observer para detectar cuando el usuario llega al final
onMount(() => {
const observer = new IntersectionObserver(
(entries) => {
const [entry] = entries;
if (entry.isIntersecting && hasMore && !loading) {
loadMore();
}
},
{
root: scrollContainer,
threshold: 0.1
}
);
if (loadingTrigger) {
observer.observe(loadingTrigger);
}
return () => {
observer.disconnect();
};
});
function handlePreviousPage() {
if (currentPage > 1) {
onPageChange(currentPage - 1);
}
}
function handleNextPage() {
const totalPages = Math.ceil(totalItems / pageSize);
if (currentPage < totalPages) {
onPageChange(currentPage + 1);
}
}
</script>
<div class="w-full">
<div class="rounded-md border">
<div class="rounded-md border max-h-[600px] overflow-y-auto" bind:this={scrollContainer}>
<Table.Root>
<Table.Header>
<Table.Header class="sticky top-0 bg-background z-10">
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
{#each headerGroup.headers as header (header.id)}
@@ -107,33 +97,27 @@
</Table.Cell>
</Table.Row>
{/each}
<!-- Loading Trigger - Se activa cuando es visible -->
{#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-muted-foreground text-sm">Cargando más...</span>
</div>
{:else}
<div class="text-muted-foreground text-sm">
Desplázate para cargar más
</div>
{/if}
</div>
</Table.Cell>
</Table.Row>
{/if}
</Table.Body>
</Table.Root>
</div>
<div class="flex items-center justify-between space-x-2 py-4">
<div class="text-muted-foreground text-sm">
Mostrando {data.length} de {totalItems} registro(s)
</div>
<div class="flex items-center space-x-2">
<div class="text-muted-foreground text-sm">
Página {currentPage} de {Math.ceil(totalItems / pageSize)}
</div>
<Button
variant="outline"
size="sm"
onclick={handlePreviousPage}
disabled={currentPage === 1}
>
Anterior
</Button>
<Button
variant="outline"
size="sm"
onclick={handleNextPage}
disabled={currentPage >= Math.ceil(totalItems / pageSize)}
>
Siguiente
</Button>
</div>
</div>
</div>