diff --git a/frontend/src/lib/components/dashboard/common/infinite-data-table.svelte b/frontend/src/lib/components/dashboard/common/infinite-data-table.svelte index 3449e32e..598dffe3 100644 --- a/frontend/src/lib/components/dashboard/common/infinite-data-table.svelte +++ b/frontend/src/lib/components/dashboard/common/infinite-data-table.svelte @@ -11,8 +11,9 @@ hasMore: boolean; loadMore: () => void; emptyMessage?: string; - selectedIds?: number[]; - onSelectedIdsChange?: (ids: number[]) => void; + selectedIds?: (number | string)[]; + onSelectedIdsChange?: (ids: (number | string)[]) => void; + getRowId?: (row: TData) => string | number; onRowClick?: (row: TData) => void; onRowDoubleClick?: (row: TData) => void; }; @@ -26,6 +27,7 @@ emptyMessage = 'No hay resultados.', selectedIds = [], onSelectedIdsChange, + getRowId, onRowClick, onRowDoubleClick, }: InfiniteDataTableProps = $props(); @@ -36,7 +38,7 @@ }, columns, getCoreRowModel: getCoreRowModel(), - getRowId: (row: any) => row.id?.toString(), + getRowId: (row: any) => (getRowId ? getRowId(row).toString() : (row.id || row.transporter_key || row.driver_id || row.trailer_number || row.vehicle_key || '').toString()), state: { get rowSelection() { const selection: Record = {}; @@ -60,8 +62,7 @@ const nextSelectedIds = Object.entries(rowSelection) .filter(([, selected]) => Boolean(selected)) - .map(([id]) => Number(id)) - .filter((id) => Number.isFinite(id)); + .map(([id]) => (isNaN(Number(id)) ? id : Number(id))); onSelectedIdsChange(nextSelectedIds); }, @@ -189,6 +190,7 @@ .filter(Boolean) .join(' ')} onclick={() => onRowClick?.(row.original)} + ondblclick={() => onRowDoubleClick?.(row.original)} > {#each visibleCells as cell (cell.id)} {@const colId = cell.column.id} diff --git a/frontend/src/lib/components/dashboard/transportation/drivers/columns.ts b/frontend/src/lib/components/dashboard/transportation/drivers/columns.ts index 35bc7c79..655d4808 100644 --- a/frontend/src/lib/components/dashboard/transportation/drivers/columns.ts +++ b/frontend/src/lib/components/dashboard/transportation/drivers/columns.ts @@ -37,16 +37,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef[] { accessorKey: 'last_name', header: 'Apellido', cell: ({ row }) => row.original.last_name || '-' - }, - { - id: 'actions', - header: 'Acciones', - cell: ({ row }) => { - return renderComponent(DataTableActions, { - item: row.original, - onSuccess - }); - } } ]; } diff --git a/frontend/src/lib/components/dashboard/transportation/trailers/columns.ts b/frontend/src/lib/components/dashboard/transportation/trailers/columns.ts index e00011c6..50eb1231 100644 --- a/frontend/src/lib/components/dashboard/transportation/trailers/columns.ts +++ b/frontend/src/lib/components/dashboard/transportation/trailers/columns.ts @@ -50,15 +50,5 @@ export function createColumns(onSuccess?: () => void): ColumnDef[] { return row.original.country || '-'; } }, - { - id: 'actions', - header: 'Acciones', - cell: ({ row }) => { - return renderComponent(DataTableActions, { - item: row.original, - onSuccess - }); - } - } ]; } diff --git a/frontend/src/lib/components/dashboard/transportation/trailers/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/transportation/trailers/create-edit-dialog.svelte index c2ecb42b..0bbb617d 100644 --- a/frontend/src/lib/components/dashboard/transportation/trailers/create-edit-dialog.svelte +++ b/frontend/src/lib/components/dashboard/transportation/trailers/create-edit-dialog.svelte @@ -160,11 +160,10 @@ void handleSubmit(); } - onMount(() => { + $effect(() => { + if (!browser) return; document.addEventListener('save-form', onSaveFormEvent); - }); - onDestroy(() => { - document.removeEventListener('save-form', onSaveFormEvent); + return () => document.removeEventListener('save-form', onSaveFormEvent); }); diff --git a/frontend/src/lib/components/dashboard/transportation/transporters/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/transportation/transporters/create-edit-dialog.svelte index a39db20b..81d953b1 100644 --- a/frontend/src/lib/components/dashboard/transportation/transporters/create-edit-dialog.svelte +++ b/frontend/src/lib/components/dashboard/transportation/transporters/create-edit-dialog.svelte @@ -169,11 +169,10 @@ void handleSubmit(); } - onMount(() => { + $effect(() => { + if (!browser) return; document.addEventListener('save-form', onSaveFormEvent); - }); - onDestroy(() => { - document.removeEventListener('save-form', onSaveFormEvent); + return () => document.removeEventListener('save-form', onSaveFormEvent); }); diff --git a/frontend/src/lib/components/dashboard/transportation/transporters/transporter-columns.ts b/frontend/src/lib/components/dashboard/transportation/transporters/transporter-columns.ts index 32e69333..e457eaf7 100644 --- a/frontend/src/lib/components/dashboard/transportation/transporters/transporter-columns.ts +++ b/frontend/src/lib/components/dashboard/transportation/transporters/transporter-columns.ts @@ -50,15 +50,5 @@ export function createColumns(onSuccess?: () => void): ColumnDef[] return row.original.transport_code || '-'; } }, - { - id: 'actions', - header: 'Acciones', - cell: ({ row }) => { - return renderComponent(DataTableActions, { - item: row.original, - onSuccess - }); - } - } ]; } diff --git a/frontend/src/lib/components/dashboard/transportation/vehicles/columns.ts b/frontend/src/lib/components/dashboard/transportation/vehicles/columns.ts index 2bacea83..34b85486 100644 --- a/frontend/src/lib/components/dashboard/transportation/vehicles/columns.ts +++ b/frontend/src/lib/components/dashboard/transportation/vehicles/columns.ts @@ -50,15 +50,5 @@ export function createColumns(onSuccess?: () => void): ColumnDef[] { return row.original.transport_type || '-'; } }, - { - id: 'actions', - header: 'Acciones', - cell: ({ row }) => { - return renderComponent(DataTableActions, { - item: row.original, - onSuccess - }); - } - } ]; } diff --git a/frontend/src/lib/components/dashboard/transportation/vehicles/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/transportation/vehicles/create-edit-dialog.svelte index 1080828b..e288a1a4 100644 --- a/frontend/src/lib/components/dashboard/transportation/vehicles/create-edit-dialog.svelte +++ b/frontend/src/lib/components/dashboard/transportation/vehicles/create-edit-dialog.svelte @@ -11,7 +11,6 @@ import { statesApi, type State } from '$lib/api/dashboard/reference_data/states'; import { companyStore } from '$lib/stores/company.svelte'; import { browser } from '$app/environment'; - import { onMount, onDestroy } from 'svelte'; let { open = $bindable(false), @@ -202,11 +201,10 @@ void handleSubmit(); } - onMount(() => { + $effect(() => { + if (!browser) return; document.addEventListener('save-form', onSaveFormEvent); - }); - onDestroy(() => { - document.removeEventListener('save-form', onSaveFormEvent); + return () => document.removeEventListener('save-form', onSaveFormEvent); }); diff --git a/frontend/src/routes/dashboard/general_catalogs/drivers/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/drivers/+page.svelte index 8f19bbcd..b0214aa2 100644 --- a/frontend/src/routes/dashboard/general_catalogs/drivers/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/drivers/+page.svelte @@ -2,7 +2,7 @@ import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; - import { Plus, RefreshCw } from 'lucide-svelte'; + import { Plus, RefreshCw, Pencil, Trash2 } from 'lucide-svelte'; import InfiniteDataTable from '$lib/components/dashboard/common/infinite-data-table.svelte'; import CreateEditDialog from '$lib/components/dashboard/transportation/drivers/create-edit-dialog.svelte'; @@ -19,6 +19,12 @@ let currentPage = $state(1); let pageSize = $state(50); let createDialogOpen = $state(false); + let editingItem = $state(null); + let selectedIds = $state<(string | number)[]>([]); + const selectedDriver = $derived( + selectedIds.length === 1 ? data.find((d) => (d.transporter_key + '-' + d.line) === selectedIds[0]) : null + ); + const hasSelection = $derived(selectedIds.length > 0); let searchTransporterKey = $state(''); let searchDriverName = $state(''); @@ -75,9 +81,70 @@ } } + function handleDialogSuccess() { + createDialogOpen = false; + editingItem = null; + loadData(); + } + + function handleRowDoubleClick(row: Driver) { + editingItem = row; + createDialogOpen = true; + } + + function handleRowClick(row: Driver) { + const id = row.transporter_key + '-' + row.line; + if (selectedIds.includes(id)) { + selectedIds = selectedIds.filter((i) => i !== id); + } else { + selectedIds = [id]; + } + } + + function handleEditSelected() { + if (selectedDriver) { + editingItem = selectedDriver; + createDialogOpen = true; + } + } + + async function handleDeleteSelected() { + if (!selectedDriver) return; + if ( + !confirm( + `¿Estás seguro de eliminar el conductor "${selectedDriver.driver_name || selectedDriver.transporter_key + '-' + selectedDriver.line}"?\n\nNota: No se puede eliminar si tiene registros relacionados.` + ) + ) { + return; + } + + if (!companyStore.activeCompany) return; + + try { + const response = await driversApi.delete( + selectedDriver.transporter_key, + selectedDriver.line, + companyStore.activeCompany.id + ); + if (response.error) { + alert(`❌ Error al eliminar:\n\n${response.error}`); + } else { + alert(`✅ Conductor eliminado correctamente`); + selectedIds = []; + loadData(); + } + } catch (e) { + console.error('Error deleting:', e); + } + } + $effect(() => { const _c = companyStore.activeCompany?.id; loadData(); + + if (!createDialogOpen) { + editingItem = null; + } }); useShortcuts('Conductores', [ @@ -106,15 +173,47 @@

Conductores

Gestión del catálogo de conductores

-
+
Listado de Conductores
- {#if loading && data.length === 0}
Cargando conductores...
{:else}
{/if}
+ {#if loading && data.length === 0}
Cargando conductores...
{:else}
(selectedIds = ids)} onRowClick={handleRowClick} onRowDoubleClick={handleRowDoubleClick} getRowId={(row) => row.transporter_key + '-' + row.line} />
{/if}
Mostrando {filteredData.length} de {totalItems} registros
- +
+ + + + + diff --git a/frontend/src/routes/dashboard/general_catalogs/trailers/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/trailers/+page.svelte index 5894f317..716448a3 100644 --- a/frontend/src/routes/dashboard/general_catalogs/trailers/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/trailers/+page.svelte @@ -4,7 +4,7 @@ import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; - import { Plus, RefreshCw } from 'lucide-svelte'; + import { Plus, RefreshCw, Pencil, Trash2 } from 'lucide-svelte'; // Importar componentes de la librería import InfiniteDataTable from '$lib/components/dashboard/common/infinite-data-table.svelte'; @@ -25,6 +25,12 @@ let pageSize = $state(50); let hasMore = $derived(data.length < totalItems); let createDialogOpen = $state(false); + let editingItem = $state(null); + let selectedIds = $state<(string | number)[]>([]); + const selectedTrailer = $derived( + selectedIds.length === 1 ? data.find((t) => t.trailer_number === selectedIds[0]) : null + ); + const hasSelection = $derived(selectedIds.length > 0); // Filtros let searchNumber = $state($page.url.searchParams.get('trailer_number') || ''); @@ -78,6 +84,62 @@ } } + function handleDialogSuccess() { + createDialogOpen = false; + editingItem = null; + loadData(); + } + + function handleRowDoubleClick(row: Trailer) { + editingItem = row; + createDialogOpen = true; + } + + function handleRowClick(row: Trailer) { + const id = row.trailer_number; + if (selectedIds.includes(id)) { + selectedIds = selectedIds.filter((i) => i !== id); + } else { + selectedIds = [id]; + } + } + + function handleEditSelected() { + if (selectedTrailer) { + editingItem = selectedTrailer; + createDialogOpen = true; + } + } + + async function handleDeleteSelected() { + if (!selectedTrailer) return; + if ( + !confirm( + `¿Estás seguro de eliminar el trailer "${selectedTrailer.trailer_number}"?\n\nNota: No se puede eliminar si tiene registros relacionados.` + ) + ) { + return; + } + + if (!companyStore.activeCompany) return; + + try { + const response = await trailersApi.delete( + selectedTrailer.trailer_number, + companyStore.activeCompany.id + ); + if (response.error) { + alert(`❌ Error al eliminar:\n\n${response.error}`); + } else { + alert(`✅ Trailer eliminado correctamente`); + selectedIds = []; + loadData(); + } + } catch (e) { + console.error('Error deleting:', e); + } + } + function handleSearch() { if (!browser) return; clearTimeout(searchTimeout); @@ -98,6 +160,10 @@ $effect(() => { const _ = { p: $page.url.href, c: companyStore.activeCompany?.id }; loadData(); + + if (!createDialogOpen) { + editingItem = null; + } }); useShortcuts('Trailers', [ @@ -126,15 +192,47 @@

Trailers

Gestión del catálogo de trailers de la compañía

-
+
Listado de Trailers
- {#if loading && data.length === 0}
Cargando trailers...
{:else}
{/if}
+ {#if loading && data.length === 0}
Cargando trailers...
{:else}
(selectedIds = ids)} onRowClick={handleRowClick} onRowDoubleClick={handleRowDoubleClick} getRowId={(row) => row.trailer_number} />
{/if}
Mostrando {data.length} de {totalItems} registros
- +
+ + + + + diff --git a/frontend/src/routes/dashboard/general_catalogs/transporters/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/transporters/+page.svelte index eb986eb3..e2586d06 100644 --- a/frontend/src/routes/dashboard/general_catalogs/transporters/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/transporters/+page.svelte @@ -4,7 +4,7 @@ import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; - import { Plus, RefreshCw } from 'lucide-svelte'; + import { Plus, RefreshCw, Pencil, Trash2 } from 'lucide-svelte'; // Importar componentes de la librería import InfiniteDataTable from '$lib/components/dashboard/common/infinite-data-table.svelte'; @@ -25,6 +25,12 @@ let pageSize = $state(50); let hasMore = $derived(data.length < totalItems); let createDialogOpen = $state(false); + let editingItem = $state(null); + let selectedIds = $state<(string | number)[]>([]); + const selectedTransporter = $derived( + selectedIds.length === 1 ? data.find((t) => t.transporter_key === selectedIds[0]) : null + ); + const hasSelection = $derived(selectedIds.length > 0); // Filtros let searchKey = $state($page.url.searchParams.get('transporter_key') || ''); @@ -77,6 +83,62 @@ loading = false; } } + + function handleDialogSuccess() { + createDialogOpen = false; + editingItem = null; + loadData(); + } + + function handleRowDoubleClick(row: Transporter) { + editingItem = row; + createDialogOpen = true; + } + + function handleRowClick(row: Transporter) { + const id = row.transporter_key; + if (selectedIds.includes(id)) { + selectedIds = selectedIds.filter((i) => i !== id); + } else { + selectedIds = [id]; // Por ahora selección única para simplificar + } + } + + function handleEditSelected() { + if (selectedTransporter) { + editingItem = selectedTransporter; + createDialogOpen = true; + } + } + + async function handleDeleteSelected() { + if (!selectedTransporter) return; + if ( + !confirm( + `¿Estás seguro de eliminar el transportista "${selectedTransporter.transporter_key}"?\n\nNota: No se puede eliminar si tiene registros relacionados.` + ) + ) { + return; + } + + if (!companyStore.activeCompany) return; + + try { + const response = await transportersApi.delete( + selectedTransporter.transporter_key, + companyStore.activeCompany.id + ); + if (response.error) { + alert(`❌ Error al eliminar:\n\n${response.error}`); + } else { + alert(`✅ Transportista eliminado correctamente`); + selectedIds = []; + loadData(); + } + } catch (e) { + console.error('Error deleting:', e); + } + } function handleSearch() { if (!browser) return; @@ -98,6 +160,10 @@ $effect(() => { const _ = { p: $page.url.href, c: companyStore.activeCompany?.id }; loadData(); + + if (!createDialogOpen) { + editingItem = null; + } }); useShortcuts('Transportistas', [ @@ -126,15 +192,47 @@

Transportistas

Gestión del catálogo de líneas transportistas

-
+
Listado de Transportistas
- {#if loading && data.length === 0}
Cargando transportistas...
{:else}
{/if}
+ {#if loading && data.length === 0}
Cargando transportistas...
{:else}
(selectedIds = ids)} onRowClick={handleRowClick} onRowDoubleClick={handleRowDoubleClick} />
{/if}
Mostrando {data.length} de {totalItems} registros
- +
+ + + + + diff --git a/frontend/src/routes/dashboard/general_catalogs/vehicles/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/vehicles/+page.svelte index 1e193fe7..660e044d 100644 --- a/frontend/src/routes/dashboard/general_catalogs/vehicles/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/vehicles/+page.svelte @@ -4,7 +4,7 @@ import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; - import { Plus, RefreshCw } from 'lucide-svelte'; + import { Plus, RefreshCw, Pencil, Trash2 } from 'lucide-svelte'; // Importar componentes de la librería import InfiniteDataTable from '$lib/components/dashboard/common/infinite-data-table.svelte'; @@ -25,6 +25,12 @@ let pageSize = $state(50); let hasMore = $derived(data.length < totalItems); let createDialogOpen = $state(false); + let editingItem = $state(null); + let selectedIds = $state<(string | number)[]>([]); + const selectedVehicle = $derived( + selectedIds.length === 1 ? data.find((v) => v.vehicle_key === selectedIds[0]) : null + ); + const hasSelection = $derived(selectedIds.length > 0); // Filtros let searchKey = $state($page.url.searchParams.get('vehicle_key') || ''); @@ -78,6 +84,62 @@ } } + function handleDialogSuccess() { + createDialogOpen = false; + editingItem = null; + loadData(); + } + + function handleRowDoubleClick(row: Vehicle) { + editingItem = row; + createDialogOpen = true; + } + + function handleRowClick(row: Vehicle) { + const id = row.vehicle_key; + if (selectedIds.includes(id)) { + selectedIds = selectedIds.filter((i) => i !== id); + } else { + selectedIds = [id]; + } + } + + function handleEditSelected() { + if (selectedVehicle) { + editingItem = selectedVehicle; + createDialogOpen = true; + } + } + + async function handleDeleteSelected() { + if (!selectedVehicle) return; + if ( + !confirm( + `¿Estás seguro de eliminar el vehículo "${selectedVehicle.vehicle_key}"?\n\nNota: No se puede eliminar si tiene registros relacionados.` + ) + ) { + return; + } + + if (!companyStore.activeCompany) return; + + try { + const response = await vehiclesApi.delete( + selectedVehicle.vehicle_key, + companyStore.activeCompany.id + ); + if (response.error) { + alert(`❌ Error al eliminar:\n\n${response.error}`); + } else { + alert(`✅ Vehículo eliminado correctamente`); + selectedIds = []; + loadData(); + } + } catch (e) { + console.error('Error deleting:', e); + } + } + function handleSearch() { if (!browser) return; clearTimeout(searchTimeout); @@ -98,6 +160,10 @@ $effect(() => { const _ = { p: $page.url.href, c: companyStore.activeCompany?.id }; loadData(); + + if (!createDialogOpen) { + editingItem = null; + } }); useShortcuts('Vehículos (transporte)', [ @@ -128,15 +194,47 @@ Gestión del catálogo de camiones y vehículos de transporte

-
+
Listado de Vehículos
- {#if loading && data.length === 0}
Cargando vehículos...
{:else}
{/if}
+ {#if loading && data.length === 0}
Cargando vehículos...
{:else}
(selectedIds = ids)} onRowClick={handleRowClick} onRowDoubleClick={handleRowDoubleClick} getRowId={(row) => row.vehicle_key} />
{/if}
Mostrando {data.length} de {totalItems} registros
- +
+ + + + +