Merge pull request 'fix/transportes-funciones' (#298) from fix/transportes-funciones into development
Reviewed-on: ADUANASOFT/anexo76#298
This commit is contained in:
@@ -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<TData, TValue> = $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<string, boolean> = {};
|
||||
@@ -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}
|
||||
|
||||
@@ -37,16 +37,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Driver>[] {
|
||||
accessorKey: 'last_name',
|
||||
header: 'Apellido',
|
||||
cell: ({ row }) => row.original.last_name || '-'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -50,15 +50,5 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Trailer>[] {
|
||||
return row.original.country || '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -50,15 +50,5 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Transporter>[]
|
||||
return row.original.transport_code || '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -50,15 +50,5 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Vehicle>[] {
|
||||
return row.original.transport_type || '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -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<Driver | null>(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 @@
|
||||
<h1 class="text-2xl font-bold tracking-tight">Conductores</h1>
|
||||
<p class="text-muted-foreground">Gestión del catálogo de conductores</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={loadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (createDialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nuevo Conductor</Button></div>
|
||||
<div class="flex items-center gap-3"><Button class="h-9" onclick={() => { editingItem = null; createDialogOpen = true; }}><Plus class="mr-2 h-4 w-4" />Nuevo Conductor</Button></div>
|
||||
</div>
|
||||
|
||||
<Card.Root class="border bg-background flex flex-col">
|
||||
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Conductores</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Clave transportista" class="h-9 w-44 bg-card lg:w-56" bind:value={searchTransporterKey} /><Input placeholder="Nombre" class="h-9 w-44 bg-card lg:w-56" bind:value={searchDriverName} /></div></div></Card.Header>
|
||||
<Card.Content class="p-0">{#if loading && data.length === 0}<div class="flex h-64 items-center justify-center text-muted-foreground">Cargando conductores...</div>{:else}<div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable data={filteredData} {columns} {loading} {hasMore} {loadMore} /></div>{/if}</Card.Content>
|
||||
<Card.Content class="p-0">{#if loading && data.length === 0}<div class="flex h-64 items-center justify-center text-muted-foreground">Cargando conductores...</div>{:else}<div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable data={filteredData} {columns} {loading} {hasMore} {loadMore} {selectedIds} onSelectedIdsChange={(ids) => (selectedIds = ids)} onRowClick={handleRowClick} onRowDoubleClick={handleRowDoubleClick} getRowId={(row) => row.transporter_key + '-' + row.line} /></div>{/if}</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {filteredData.length} de {totalItems} registros</div>
|
||||
|
||||
<CreateEditDialog bind:open={createDialogOpen} onSuccess={loadData} />
|
||||
<div class="h-20"></div>
|
||||
|
||||
<!-- Footer fijo con botones de acción -->
|
||||
<div
|
||||
id="drivers-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEditSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDeleteSelected}
|
||||
disabled={selectedIds.length === 0}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog bind:open={createDialogOpen} item={editingItem} onSuccess={handleDialogSuccess} />
|
||||
</div>
|
||||
|
||||
@@ -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<Trailer | null>(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 @@
|
||||
<h1 class="text-2xl font-bold tracking-tight">Trailers</h1>
|
||||
<p class="text-muted-foreground">Gestión del catálogo de trailers de la compañía</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={loadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (createDialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nuevo Trailer</Button></div>
|
||||
<div class="flex items-center gap-3"><Button class="h-9" onclick={() => { editingItem = null; createDialogOpen = true; }}><Plus class="mr-2 h-4 w-4" />Nuevo Trailer</Button></div>
|
||||
</div>
|
||||
|
||||
<Card.Root class="border bg-background flex flex-col">
|
||||
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Trailers</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Número" class="h-9 w-40 bg-card lg:w-52" bind:value={searchNumber} oninput={handleSearch} /><Input placeholder="Placas" class="h-9 w-40 bg-card lg:w-52" bind:value={searchPlate} oninput={handleSearch} /></div></div></Card.Header>
|
||||
<Card.Content class="p-0">{#if loading && data.length === 0}<div class="flex h-64 items-center justify-center text-muted-foreground">Cargando trailers...</div>{:else}<div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable {data} {columns} {loading} {hasMore} {loadMore} /></div>{/if}</Card.Content>
|
||||
<Card.Content class="p-0">{#if loading && data.length === 0}<div class="flex h-64 items-center justify-center text-muted-foreground">Cargando trailers...</div>{:else}<div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable {data} {columns} {loading} {hasMore} {loadMore} {selectedIds} onSelectedIdsChange={(ids) => (selectedIds = ids)} onRowClick={handleRowClick} onRowDoubleClick={handleRowDoubleClick} getRowId={(row) => row.trailer_number} /></div>{/if}</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {data.length} de {totalItems} registros</div>
|
||||
|
||||
<CreateEditDialog bind:open={createDialogOpen} onSuccess={loadData} />
|
||||
<div class="h-20"></div>
|
||||
|
||||
<!-- Footer fijo con botones de acción -->
|
||||
<div
|
||||
id="trailers-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEditSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDeleteSelected}
|
||||
disabled={selectedIds.length === 0}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog bind:open={createDialogOpen} item={editingItem} onSuccess={handleDialogSuccess} />
|
||||
</div>
|
||||
|
||||
@@ -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<Transporter | null>(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 @@
|
||||
<h1 class="text-2xl font-bold tracking-tight">Transportistas</h1>
|
||||
<p class="text-muted-foreground">Gestión del catálogo de líneas transportistas</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={loadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (createDialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nuevo Transportista</Button></div>
|
||||
<div class="flex items-center gap-3"><Button class="h-9" onclick={() => { editingItem = null; createDialogOpen = true; }}><Plus class="mr-2 h-4 w-4" />Nuevo Transportista</Button></div>
|
||||
</div>
|
||||
|
||||
<Card.Root class="border bg-background flex flex-col">
|
||||
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Transportistas</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Clave" class="h-9 w-40 bg-card lg:w-52" bind:value={searchKey} oninput={handleSearch} /><Input placeholder="Nombre" class="h-9 w-44 bg-card lg:w-56" bind:value={searchName} oninput={handleSearch} /></div></div></Card.Header>
|
||||
<Card.Content class="p-0">{#if loading && data.length === 0}<div class="flex h-64 items-center justify-center text-muted-foreground">Cargando transportistas...</div>{:else}<div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable {data} {columns} {loading} {hasMore} {loadMore} /></div>{/if}</Card.Content>
|
||||
<Card.Content class="p-0">{#if loading && data.length === 0}<div class="flex h-64 items-center justify-center text-muted-foreground">Cargando transportistas...</div>{:else}<div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable {data} {columns} {loading} {hasMore} {loadMore} {selectedIds} onSelectedIdsChange={(ids) => (selectedIds = ids)} onRowClick={handleRowClick} onRowDoubleClick={handleRowDoubleClick} /></div>{/if}</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {data.length} de {totalItems} registros</div>
|
||||
|
||||
<CreateEditDialog bind:open={createDialogOpen} onSuccess={loadData} />
|
||||
<div class="h-20"></div>
|
||||
|
||||
<!-- Footer fijo con botones de acción -->
|
||||
<div
|
||||
id="transporters-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEditSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDeleteSelected}
|
||||
disabled={selectedIds.length === 0}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog bind:open={createDialogOpen} item={editingItem} onSuccess={handleDialogSuccess} />
|
||||
</div>
|
||||
|
||||
@@ -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<Vehicle | null>(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
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={loadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (createDialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nuevo Vehículo</Button></div>
|
||||
<div class="flex items-center gap-3"><Button class="h-9" onclick={() => { editingItem = null; createDialogOpen = true; }}><Plus class="mr-2 h-4 w-4" />Nuevo Vehículo</Button></div>
|
||||
</div>
|
||||
|
||||
<Card.Root class="border bg-background flex flex-col">
|
||||
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Vehículos</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Clave" class="h-9 w-40 bg-card lg:w-52" bind:value={searchKey} oninput={handleSearch} /><Input placeholder="Placas" class="h-9 w-40 bg-card lg:w-52" bind:value={searchPlate} oninput={handleSearch} /></div></div></Card.Header>
|
||||
<Card.Content class="p-0">{#if loading && data.length === 0}<div class="flex h-64 items-center justify-center text-muted-foreground">Cargando vehículos...</div>{:else}<div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable {data} {columns} {loading} {hasMore} {loadMore} /></div>{/if}</Card.Content>
|
||||
<Card.Content class="p-0">{#if loading && data.length === 0}<div class="flex h-64 items-center justify-center text-muted-foreground">Cargando vehículos...</div>{:else}<div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable {data} {columns} {loading} {hasMore} {loadMore} {selectedIds} onSelectedIdsChange={(ids) => (selectedIds = ids)} onRowClick={handleRowClick} onRowDoubleClick={handleRowDoubleClick} getRowId={(row) => row.vehicle_key} /></div>{/if}</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {data.length} de {totalItems} registros</div>
|
||||
|
||||
<CreateEditDialog bind:open={createDialogOpen} onSuccess={loadData} />
|
||||
<div class="h-20"></div>
|
||||
|
||||
<!-- Footer fijo con botones de acción -->
|
||||
<div
|
||||
id="vehicles-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEditSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDeleteSelected}
|
||||
disabled={selectedIds.length === 0}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog bind:open={createDialogOpen} item={editingItem} onSuccess={handleDialogSuccess} />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user