fix(pedimentos): standardize ID naming convention and enhance row selection functionality in data table

This commit is contained in:
Galindo97
2026-01-21 17:32:37 -06:00
parent e28a81c07c
commit 18559025b1
5 changed files with 168 additions and 17 deletions

View File

@@ -324,5 +324,5 @@ export const pedimentosApi = {
* @param id - ID del pedimento a eliminar
* @param companyId - ID de la compañía (por defecto 1)
*/
delete: (id: number, companyId = 1) => api.delete(`/v1/a76/pedimentos/${id}/?company_id=${companyId}`)
delete: (id: number, companyId = 1) => api.delete(`/v1/a76/pedimentos/${id}?company_id=${companyId}`)
};

View File

@@ -1,7 +1,6 @@
import type { ColumnDef } from "@tanstack/table-core";
import { renderComponent, renderSnippet } from "$lib/components/ui/data-table/index.js";
import { createRawSnippet } from "svelte";
import DataTableActions from "./data-table-actions.svelte";
import type { Pedimento } from "$lib/api/dashboard/a76/pedimentos";
// Extender el tipo ColumnMeta para incluir className
@@ -93,6 +92,33 @@ function getStatusColor(status?: string | null): string {
export function createColumns(onSuccess?: () => void): ColumnDef<Pedimento>[] {
return [
{
id: "select",
header: ({ table }) => {
return renderSnippet(
createRawSnippet(() => ({
render: () => `<div class="w-4"></div>`
}))
);
},
cell: ({ row }) => {
const isSelected = row.getIsSelected();
const checkboxSnippet = createRawSnippet<[{ selected: boolean }]>((getProps) => {
const { selected } = getProps();
return {
render: () => `<div class="flex items-center justify-center">
<input type="checkbox" class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary cursor-pointer" ${selected ? 'checked' : ''} />
</div>`
};
});
return renderSnippet(checkboxSnippet, { selected: isSelected });
},
size: 40,
enableSorting: false,
enableHiding: false
},
{
accessorKey: "id",
header: "ID",
@@ -214,7 +240,7 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Pedimento>[] {
return renderSnippet(dateSnippet, { date: formatDate(row.original.pedimento_dates?.payment_date) });
}
},
{
/*{
accessorKey: "pedimento_config_update_rectification.pediment_rectifed_18",
header: "Pedimento 18",
meta: { className: "hidden lg:table-cell" },
@@ -228,8 +254,8 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Pedimento>[] {
});
return renderSnippet(ped18Snippet, { value: row.original.pedimento_config_update_rectification?.pediment_rectifed_18 });
}
},
{
},*/
/*{
accessorKey: "pedimento_config_update_rectification.r1",
header: "Pedimento R1",
meta: { className: "hidden lg:table-cell" },
@@ -243,7 +269,7 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Pedimento>[] {
});
return renderSnippet(r1Snippet, { value: row.original.pedimento_config_update_rectification?.r1 });
}
},
},*/
{
accessorKey: "pedimento_validation.electronic_signature",
header: "Acuse Electrónico",
@@ -397,13 +423,8 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Pedimento>[] {
});
return renderSnippet(dateSnippet, { date: formatDate(row.original.created_at) });
}
},
{
id: "actions",
cell: ({ row }) => {
return renderComponent(DataTableActions, { item: row.original, onSuccess });
}
}
// Columna de acciones eliminada - ahora usamos botones en el footer
];
}

View File

@@ -2,10 +2,13 @@
import { onMount } from 'svelte';
import {
type ColumnDef,
getCoreRowModel
getCoreRowModel,
type RowSelectionState
} 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";
import { Edit } from "lucide-svelte";
type DataTableProps<TData, TValue> = {
columns: ColumnDef<TData, TValue>[];
@@ -13,6 +16,8 @@
loading: boolean;
hasMore: boolean;
loadMore: () => void;
selectedId?: number | null;
onRowClick?: (row: TData) => void;
};
let {
@@ -20,7 +25,9 @@
columns,
loading,
hasMore,
loadMore
loadMore,
selectedId = null,
onRowClick
}: DataTableProps<TData, TValue> = $props();
const table = createSvelteTable({
@@ -28,12 +35,28 @@
return data;
},
columns,
getCoreRowModel: getCoreRowModel()
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>();
// Función para manejar doble clic en una fila
function handleRowDoubleClick(row: any) {
const pedimento = row.original;
if (pedimento?.id) {
window.location.href = `/dashboard/pedimentos/edit/${pedimento.id}`;
}
}
// Intersection Observer para detectar cuando el usuario llega al final
onMount(() => {
const observer = new IntersectionObserver(
@@ -80,7 +103,16 @@
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row data-state={row.getIsSelected() && "selected"}>
<Table.Row
data-state={row.getIsSelected() && "selected"}
onclick={() => {
if (onRowClick) {
onRowClick(row.original);
}
}}
ondblclick={() => handleRowDoubleClick(row)}
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 {cell.column.columnDef.meta?.className || ''}">
<FlexRender

View File

@@ -7,10 +7,12 @@
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import * as Dialog from '$lib/components/ui/dialog';
import type { PageData } from './$types';
import { browser } from '$app/environment';
import { companyStore } from '$lib/stores/company.svelte';
import { Plus, Filter, Trash2, RefreshCw } from 'lucide-svelte';
import { Edit } from 'lucide-svelte';
// Los datos iniciales vienen del servidor
let { data }: { data: PageData } = $props();
@@ -72,6 +74,59 @@
let loading = $state(false);
let hasMore = $derived(allItems.length < totalItems);
let error = $state<string | null>(data.error || null);
// Estado para selección de filas
let selectedId = $state<number | null>(null);
let hasSelection = $derived(selectedId !== null);
let showDeleteDialog = $state(false);
function handleRowClick(pedimento: Pedimento) {
// Toggle: si ya está seleccionado, deseleccionar; si no, seleccionar
selectedId = selectedId === pedimento.id ? null : pedimento.id;
console.log('🔘 Row clicked, pedimento.id:', pedimento.id, 'selectedId:', selectedId, 'hasSelection:', hasSelection);
}
function handleEditSelected() {
if (selectedId) {
window.location.href = `/dashboard/pedimentos/edit/${selectedId}`;
}
}
function handleDelete() {
if (!selectedId) {
return;
}
showDeleteDialog = true;
}
async function confirmDelete() {
if (!selectedId) return;
const companyId = companyStore.activeCompany?.id;
if (!companyId) {
error = 'No hay compañía seleccionada';
return;
}
try {
const response = await pedimentosApi.delete(selectedId, companyId);
if (response.error) {
console.error('🗑️ [Pedimentos] Error al eliminar:', response.error);
error = response.error;
return;
}
// Recargar datos
await reloadData();
selectedId = null;
showDeleteDialog = false;
} catch (e) {
console.error('🗑️ [Pedimentos] Error deleting:', e);
error = 'Error al eliminar el pedimento';
}
}
async function loadMore() {
if (loading || !hasMore) return;
@@ -343,7 +398,50 @@
{loading}
{hasMore}
{loadMore}
{selectedId}
onRowClick={handleRowClick}
/>
</Card.Content>
</Card.Root>
<!-- Footer fijo con botones de acción -->
<div class="fixed bottom-0 left-0 right-0 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/80 border-t shadow-lg z-[5]">
<div class="px-4 py-4 max-w-[1400px] mx-auto">
<!-- Botones de acción -->
<div class="flex justify-end gap-2">
<Button size="sm" onclick={handleCreateClick}>
<Plus class="h-4 w-4 mr-1" />
Insertar
</Button>
<Button variant="outline" size="sm" onclick={handleEditSelected} disabled={!hasSelection}>
<Edit size={16} class="mr-1" />
Editar
</Button>
<Button variant="outline" size="sm" onclick={handleDelete} disabled={!hasSelection}>
<Trash2 size={16} class="mr-1" />
Borrar
</Button>
</div>
</div>
</div>
</div>
<!-- Diálogo de confirmación para borrar -->
<Dialog.Root bind:open={showDeleteDialog}>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>¿Eliminar pedimento?</Dialog.Title>
<Dialog.Description>
Esta acción no se puede deshacer. El pedimento será eliminado permanentemente.
</Dialog.Description>
</Dialog.Header>
<div class="flex justify-end gap-3 mt-4">
<Button variant="outline" onclick={() => showDeleteDialog = false}>
Cancelar
</Button>
<Button variant="destructive" onclick={confirmDelete}>
Eliminar
</Button>
</div>
</Dialog.Content>
</Dialog.Root>