Merge pull request 'fix/bultos-embalajes' (#301) from fix/bultos-embalajes into development
Reviewed-on: ADUANASOFT/anexo76#301
This commit is contained in:
@@ -1,71 +1,57 @@
|
||||
/**
|
||||
* Definición de columnas para la tabla de Packages
|
||||
*/
|
||||
import type { Package } from '$lib/api/dashboard/a76/general_catalogs/packages';
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Package>[] {
|
||||
|
||||
export function createColumns(): ColumnDef<Package>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'key',
|
||||
header: 'Clave',
|
||||
cell: ({ row }) => {
|
||||
return row.original.key;
|
||||
{
|
||||
accessorKey: 'key',
|
||||
header: 'Clave',
|
||||
cell: ({ row }) => {
|
||||
return row.original.key;
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'description_es',
|
||||
header: 'Descripción (ES)',
|
||||
cell: ({ row }) => {
|
||||
return row.original.description_es || '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'description_en',
|
||||
header: 'Descripción (EN)',
|
||||
cell: ({ row }) => {
|
||||
return row.original.description_en || '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'weight_unit',
|
||||
header: 'Peso Unitario',
|
||||
cell: ({ row }) => {
|
||||
return row.original.weight_unit ? row.original.weight_unit.toString() : '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'plurals',
|
||||
header: 'Plural',
|
||||
cell: ({ row }) => {
|
||||
return row.original.plurals || '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'code_ace',
|
||||
header: 'Código ACE',
|
||||
cell: ({ row }) => {
|
||||
return row.original.code_ace || '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'code_aamex',
|
||||
header: 'Código AAMEX',
|
||||
cell: ({ row }) => {
|
||||
return row.original.code_aamex || '-';
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'description_es',
|
||||
header: 'Descripción (ES)',
|
||||
cell: ({ row }) => {
|
||||
return row.original.description_es || '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'description_en',
|
||||
header: 'Descripción (EN)',
|
||||
cell: ({ row }) => {
|
||||
return row.original.description_en || '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'weight_unit',
|
||||
header: 'Peso Unitario',
|
||||
cell: ({ row }) => {
|
||||
return row.original.weight_unit ? row.original.weight_unit.toString() : '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'plurals',
|
||||
header: 'Plural',
|
||||
cell: ({ row }) => {
|
||||
return row.original.plurals || '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'code_ace',
|
||||
header: 'Código ACE',
|
||||
cell: ({ row }) => {
|
||||
return row.original.code_ace || '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'code_aamex',
|
||||
header: 'Código AAMEX',
|
||||
cell: ({ row }) => {
|
||||
return row.original.code_aamex || '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -6,32 +6,44 @@
|
||||
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 { goto } from '$app/navigation';
|
||||
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import * as m from '$lib/paraglide/messages.js';
|
||||
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosListaBultos } from '$lib/config/shortcuts/dashboard/general_catalogs/packages/list';
|
||||
import { getPackages } from '$lib/api/dashboard/a76/general_catalogs/packages';
|
||||
import {
|
||||
deletePackage,
|
||||
getPackages,
|
||||
type Package
|
||||
} from '$lib/api/dashboard/a76/general_catalogs/packages';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
let createDialogOpen = $state(false);
|
||||
let editingItem = $state<Package | null>(null);
|
||||
let error = $state<string | null>(data.error || null);
|
||||
let selectedIds = $state<(string | number)[]>([]);
|
||||
const selectedPackage = $derived(
|
||||
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
|
||||
);
|
||||
|
||||
// Filtros
|
||||
let searchCode = $state($page.url.searchParams.get('code') || '');
|
||||
let searchName = $state($page.url.searchParams.get('name') || '');
|
||||
let searchCode = $state($page.url.searchParams.get('key') || '');
|
||||
let searchDescription = $state($page.url.searchParams.get('description_es') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
// Atajos
|
||||
useShortcuts(
|
||||
'Lista Bultos',
|
||||
obtenerAtajosListaBultos({
|
||||
manejarNuevo: () => (createDialogOpen = true),
|
||||
manejarActualizar: handleSuccess,
|
||||
manejarNuevo: () => {
|
||||
editingItem = null;
|
||||
createDialogOpen = true;
|
||||
},
|
||||
manejarActualizar: reloadData,
|
||||
manejarEditar: handleEditSelected,
|
||||
manejarEliminar: handleDeleteSelected,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -60,14 +72,15 @@
|
||||
error = null;
|
||||
try {
|
||||
const filters: Record<string, string> = {};
|
||||
if (searchCode) filters.code = searchCode;
|
||||
if (searchName) filters.name = searchName;
|
||||
if (searchCode) filters.key = searchCode;
|
||||
if (searchDescription) filters.description_es = searchDescription;
|
||||
|
||||
const response = await getPackages(1, pageSize, companyStore.activeCompany.id, filters);
|
||||
if (response.data) {
|
||||
allItems = response.data.items;
|
||||
currentPage = 1;
|
||||
totalItems = response.data.total;
|
||||
selectedIds = [];
|
||||
}
|
||||
} catch (err) {
|
||||
error = 'Error aplicando filtros';
|
||||
@@ -77,11 +90,11 @@
|
||||
}
|
||||
|
||||
const url = new URL($page.url);
|
||||
if (searchCode) url.searchParams.set('code', searchCode);
|
||||
else url.searchParams.delete('code');
|
||||
if (searchCode) url.searchParams.set('key', searchCode);
|
||||
else url.searchParams.delete('key');
|
||||
|
||||
if (searchName) url.searchParams.set('name', searchName);
|
||||
else url.searchParams.delete('name');
|
||||
if (searchDescription) url.searchParams.set('description_es', searchDescription);
|
||||
else url.searchParams.delete('description_es');
|
||||
|
||||
history.replaceState(history.state, '', url);
|
||||
}, 500);
|
||||
@@ -93,8 +106,8 @@
|
||||
error = null;
|
||||
try {
|
||||
const filters: Record<string, string> = {};
|
||||
if (searchCode) filters.code = searchCode;
|
||||
if (searchName) filters.name = searchName;
|
||||
if (searchCode) filters.key = searchCode;
|
||||
if (searchDescription) filters.description_es = searchDescription;
|
||||
|
||||
const response = await getPackages(
|
||||
currentPage + 1,
|
||||
@@ -121,14 +134,15 @@
|
||||
error = null;
|
||||
try {
|
||||
const filters: Record<string, string> = {};
|
||||
if (searchCode) filters.code = searchCode;
|
||||
if (searchName) filters.name = searchName;
|
||||
if (searchCode) filters.key = searchCode;
|
||||
if (searchDescription) filters.description_es = searchDescription;
|
||||
|
||||
const response = await getPackages(1, pageSize, companyStore.activeCompany.id, filters);
|
||||
if (response.data) {
|
||||
allItems = response.data.items;
|
||||
currentPage = 1;
|
||||
totalItems = response.data.total;
|
||||
selectedIds = [];
|
||||
}
|
||||
} catch (err) {
|
||||
error = 'Error al recargar datos';
|
||||
@@ -139,8 +153,65 @@
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
createDialogOpen = false;
|
||||
editingItem = null;
|
||||
selectedIds = [];
|
||||
reloadData();
|
||||
}
|
||||
|
||||
function handleRowClick(row: Package) {
|
||||
const id = row.id;
|
||||
selectedIds = selectedIds.includes(id) ? [] : [id];
|
||||
}
|
||||
|
||||
function handleRowDoubleClick(row: Package) {
|
||||
editingItem = row;
|
||||
createDialogOpen = true;
|
||||
}
|
||||
|
||||
function handleEditSelected() {
|
||||
if (!selectedPackage) return;
|
||||
editingItem = selectedPackage;
|
||||
createDialogOpen = true;
|
||||
}
|
||||
|
||||
async function handleDeleteSelected() {
|
||||
if (!selectedPackage || !companyStore.activeCompany) return;
|
||||
|
||||
if (
|
||||
!confirm(
|
||||
`¿Estás seguro de eliminar el bulto "${selectedPackage.key}"?\n\nNota: No se puede eliminar si tiene registros relacionados.`
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deletePackage(selectedPackage.id, companyStore.activeCompany.id);
|
||||
|
||||
if (response.error) {
|
||||
error = response.error;
|
||||
return;
|
||||
}
|
||||
|
||||
selectedIds = [];
|
||||
await reloadData();
|
||||
} catch (err) {
|
||||
error = 'Error al eliminar el bulto';
|
||||
console.error('Error deleting package:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (!createDialogOpen) {
|
||||
editingItem = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
|
||||
@@ -150,11 +221,13 @@
|
||||
<p class="text-muted-foreground">Gestión del catálogo de bultos y embalajes</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Button variant="outline" size="sm" class="h-9" onclick={reloadData}>
|
||||
<RefreshCw class="mr-2 h-4 w-4" />
|
||||
Actualizar
|
||||
</Button>
|
||||
<Button class="h-9" onclick={() => (createDialogOpen = true)}>
|
||||
<Button
|
||||
class="h-9"
|
||||
onclick={() => {
|
||||
editingItem = null;
|
||||
createDialogOpen = true;
|
||||
}}
|
||||
>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo Bulto
|
||||
</Button>
|
||||
@@ -180,7 +253,7 @@
|
||||
/>
|
||||
<Input
|
||||
placeholder="Descripción"
|
||||
bind:value={searchName}
|
||||
bind:value={searchDescription}
|
||||
oninput={handleSearch}
|
||||
class="h-9 w-44 bg-card lg:w-64"
|
||||
/>
|
||||
@@ -191,10 +264,14 @@
|
||||
<div class="rounded-md border bg-background overflow-hidden">
|
||||
<InfiniteDataTable
|
||||
data={allItems}
|
||||
columns={createColumns(handleSuccess)}
|
||||
columns={createColumns()}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
{selectedIds}
|
||||
onSelectedIdsChange={(ids) => (selectedIds = ids)}
|
||||
onRowClick={handleRowClick}
|
||||
onRowDoubleClick={handleRowDoubleClick}
|
||||
/>
|
||||
</div>
|
||||
</Card.Content>
|
||||
@@ -204,5 +281,36 @@
|
||||
Mostrando {allItems.length} de {totalItems} registros
|
||||
</div>
|
||||
|
||||
<CreateEditDialog bind:open={createDialogOpen} onSuccess={handleSuccess} />
|
||||
<div class="h-20"></div>
|
||||
|
||||
<div
|
||||
id="packages-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 !== 1}
|
||||
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={handleSuccess} />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user