diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimentos.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimentos.py index d4edfaee..fbc8f887 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimentos.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimentos.py @@ -16,7 +16,7 @@ router = TenantCRUDRoutes( prefix="", # No prefix here, will be added in main router tags=["a76 / pedimentos"], # Tag for Swagger documentation resource_name="Pedimento", - id_name="pedimento_id", + id_name="id", # Use standard REST convention enable_list=True, # Enable GET / with pagination enable_filters=True, # Enable status, client_id, year filters default_page_size=50, diff --git a/frontend/src/app.css b/frontend/src/app.css index 8d9a66ab..6c344082 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -118,6 +118,6 @@ @apply border-border outline-ring/50; } body { - @apply bg-background text-foreground; + @apply bg-background text-foreground overflow-x-hidden; } } diff --git a/frontend/src/lib/api/dashboard/a76/pedimentos.ts b/frontend/src/lib/api/dashboard/a76/pedimentos.ts index 574c447f..a5069c9f 100644 --- a/frontend/src/lib/api/dashboard/a76/pedimentos.ts +++ b/frontend/src/lib/api/dashboard/a76/pedimentos.ts @@ -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}`) }; diff --git a/frontend/src/lib/components/dashboard/pedimentos/columns.ts b/frontend/src/lib/components/dashboard/pedimentos/columns.ts index 2bdfbe51..3c085396 100644 --- a/frontend/src/lib/components/dashboard/pedimentos/columns.ts +++ b/frontend/src/lib/components/dashboard/pedimentos/columns.ts @@ -1,9 +1,15 @@ 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 +declare module "@tanstack/table-core" { + interface ColumnMeta { + className?: string; + } +} + /** * Formatea un número como moneda */ @@ -86,6 +92,33 @@ function getStatusColor(status?: string | null): string { export function createColumns(onSuccess?: () => void): ColumnDef[] { return [ + { + id: "select", + header: ({ table }) => { + return renderSnippet( + createRawSnippet(() => ({ + render: () => `
` + })) + ); + }, + cell: ({ row }) => { + const isSelected = row.getIsSelected(); + + const checkboxSnippet = createRawSnippet<[{ selected: boolean }]>((getProps) => { + const { selected } = getProps(); + return { + render: () => `
+ +
` + }; + }); + + return renderSnippet(checkboxSnippet, { selected: isSelected }); + }, + size: 40, + enableSorting: false, + enableHiding: false + }, { accessorKey: "id", header: "ID", @@ -120,6 +153,7 @@ export function createColumns(onSuccess?: () => void): ColumnDef[] { { accessorKey: "pedimento_type", header: "Tipo", + meta: { className: "hidden md:table-cell" }, cell: ({ row }) => { const typeSnippet = createRawSnippet<[{ type?: string | null }]>((getType) => { const { type } = getType(); @@ -134,6 +168,7 @@ export function createColumns(onSuccess?: () => void): ColumnDef[] { { accessorKey: "pedimento_code", header: "Clave", + meta: { className: "hidden lg:table-cell" }, cell: ({ row }) => { const codeSnippet = createRawSnippet<[{ code?: string | null }]>((getCode) => { const { code } = getCode(); @@ -148,6 +183,7 @@ export function createColumns(onSuccess?: () => void): ColumnDef[] { { accessorKey: "regime", header: "Régimen", + meta: { className: "hidden lg:table-cell" }, cell: ({ row }) => { const regimeSnippet = createRawSnippet<[{ regime?: string | null }]>((getRegime) => { const { regime } = getRegime(); @@ -162,6 +198,7 @@ export function createColumns(onSuccess?: () => void): ColumnDef[] { { accessorKey: "pedimento_dates.start_date", header: "Fecha Inicio", + meta: { className: "hidden xl:table-cell" }, cell: ({ row }) => { const dateSnippet = createRawSnippet<[{ date: string }]>((getDate) => { const { date } = getDate(); @@ -176,6 +213,7 @@ export function createColumns(onSuccess?: () => void): ColumnDef[] { { accessorKey: "pedimento_dates.end_date", header: "Fecha Final", + meta: { className: "hidden xl:table-cell" }, cell: ({ row }) => { const dateSnippet = createRawSnippet<[{ date: string }]>((getDate) => { const { date } = getDate(); @@ -190,6 +228,7 @@ export function createColumns(onSuccess?: () => void): ColumnDef[] { { accessorKey: "pedimento_dates.payment_date", header: "Fecha de Pago", + meta: { className: "hidden xl:table-cell" }, cell: ({ row }) => { const dateSnippet = createRawSnippet<[{ date: string }]>((getDate) => { const { date } = getDate(); @@ -201,9 +240,10 @@ export function createColumns(onSuccess?: () => void): ColumnDef[] { 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" }, cell: ({ row }) => { const ped18Snippet = createRawSnippet<[{ value?: string | null }]>((getValue) => { const { value } = getValue(); @@ -214,10 +254,11 @@ export function createColumns(onSuccess?: () => void): ColumnDef[] { }); 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" }, cell: ({ row }) => { const r1Snippet = createRawSnippet<[{ value?: string | null }]>((getValue) => { const { value } = getValue(); @@ -228,10 +269,11 @@ export function createColumns(onSuccess?: () => void): ColumnDef[] { }); return renderSnippet(r1Snippet, { value: row.original.pedimento_config_update_rectification?.r1 }); } - }, + },*/ { accessorKey: "pedimento_validation.electronic_signature", header: "Acuse Electrónico", + meta: { className: "hidden xl:table-cell" }, cell: ({ row }) => { const ackSnippet = createRawSnippet<[{ value?: string | null }]>((getValue) => { const { value } = getValue(); @@ -249,6 +291,7 @@ export function createColumns(onSuccess?: () => void): ColumnDef[] { { accessorKey: "pedimento_payments.total_contributions", header: "¿Se pagó el impuesto?", + meta: { className: "hidden xl:table-cell" }, cell: ({ row }) => { const paidSnippet = createRawSnippet<[{ value?: string | null }]>((getValue) => { const { value } = getValue(); @@ -266,6 +309,7 @@ export function createColumns(onSuccess?: () => void): ColumnDef[] { { accessorKey: "client_id", header: "Cliente", + meta: { className: "hidden md:table-cell" }, cell: ({ row }) => { const clientSnippet = createRawSnippet<[{ clientId?: number | null }]>((getClient) => { const { clientId } = getClient(); @@ -309,6 +353,7 @@ export function createColumns(onSuccess?: () => void): ColumnDef[] { }); return renderSnippet(headerSnippet, {}); }, + meta: { className: "hidden lg:table-cell" }, cell: ({ row }) => { const valueSnippet = createRawSnippet<[{ value: string }]>((getValue) => { const { value } = getValue(); @@ -330,6 +375,7 @@ export function createColumns(onSuccess?: () => void): ColumnDef[] { }); return renderSnippet(headerSnippet, {}); }, + meta: { className: "hidden xl:table-cell" }, cell: ({ row }) => { const priceSnippet = createRawSnippet<[{ price: string }]>((getPrice) => { const { price } = getPrice(); @@ -351,6 +397,7 @@ export function createColumns(onSuccess?: () => void): ColumnDef[] { }); return renderSnippet(headerSnippet, {}); }, + meta: { className: "hidden lg:table-cell" }, cell: ({ row }) => { const weightSnippet = createRawSnippet<[{ weight: string }]>((getWeight) => { const { weight } = getWeight(); @@ -365,6 +412,7 @@ export function createColumns(onSuccess?: () => void): ColumnDef[] { { accessorKey: "created_at", header: "Fecha de Creación", + meta: { className: "hidden 2xl:table-cell" }, cell: ({ row }) => { const dateSnippet = createRawSnippet<[{ date: string }]>((getDate) => { const { date } = getDate(); @@ -375,13 +423,8 @@ export function createColumns(onSuccess?: () => void): ColumnDef[] { }); 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 ]; } diff --git a/frontend/src/lib/components/dashboard/pedimentos/data-table.svelte b/frontend/src/lib/components/dashboard/pedimentos/data-table.svelte index 4c448e3d..8f4bfe0d 100644 --- a/frontend/src/lib/components/dashboard/pedimentos/data-table.svelte +++ b/frontend/src/lib/components/dashboard/pedimentos/data-table.svelte @@ -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 = { columns: ColumnDef[]; @@ -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 = $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(); let loadingTrigger = $state(); + // 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( @@ -60,13 +83,13 @@
-
- - +
+ + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} {#each headerGroup.headers as header (header.id)} - + {#if !header.isPlaceholder} {#each table.getRowModel().rows as row (row.id)} - + { + 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)} - + - +
@@ -55,7 +55,7 @@ -->
-
+
{@render children()}
diff --git a/frontend/src/routes/dashboard/pedimentos/+page.svelte b/frontend/src/routes/dashboard/pedimentos/+page.svelte index 28b09205..5ee33571 100644 --- a/frontend/src/routes/dashboard/pedimentos/+page.svelte +++ b/frontend/src/routes/dashboard/pedimentos/+page.svelte @@ -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(data.error || null); + + // Estado para selección de filas + let selectedId = $state(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} /> + + +
+
+ +
+ + + +
+
+
+ + + + + + ¿Eliminar pedimento? + + Esta acción no se puede deshacer. El pedimento será eliminado permanentemente. + + +
+ + +
+
+