feat: update sidebar navigation for fixed asset classes and add tariff fractions page

This commit is contained in:
AlexeerCT
2026-01-08 21:14:16 -06:00
parent 8dca19413b
commit 25c51120f9
2 changed files with 213 additions and 13 deletions

View File

@@ -298,7 +298,7 @@ export function getSidebarData(): SidebarData {
items: [
{
title: m["sidebar.goods.classes"](),
url: "/dashboard/goods/classes",
url: "/dashboard/goods/fixed-asset-classes",
},
{
title: m["sidebar.goods.parts"](),
@@ -386,18 +386,7 @@ export function getSidebarData(): SidebarData {
url: "/dashboard/customs_brokers",
icon: BadgeCheck,
items: [],
},
{
title: "Mercancías",
url: "#",
icon: Package,
items: [
{
title: "Catálogo de Clases de Activo Fijo",
url: "/dashboard/catalogs/fixed-asset-classes",
},
],
},
},
{
title: m["sidebar.reference_data.configuracion"](),
url: "#",

View File

@@ -0,0 +1,211 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import * as Card from '$lib/components/ui/card';
import * as Table from '$lib/components/ui/table';
import { Search, Loader2, Download } from 'lucide-svelte';
import { getTariffFractions, type TariffFraction } from '$lib/api/dashboard/a76/general_catalogs/tariff-fractions';
import { companyStore } from '$lib/stores/company.svelte';
import { onMount } from 'svelte';
let tariffFractions = $state<TariffFraction[]>([]);
let filteredFractions = $state<TariffFraction[]>([]);
let searchQuery = $state('');
let isLoading = $state(false);
let currentPage = $state(1);
let totalPages = $state(1);
let totalRecords = $state(0);
const pageSize = 50;
onMount(() => {
loadTariffFractions();
});
// Filtrar fracciones cuando cambia la búsqueda
$effect(() => {
if (searchQuery.trim() === '') {
filteredFractions = tariffFractions;
} else {
const query = searchQuery.toLowerCase();
filteredFractions = tariffFractions.filter(
(fraction) =>
fraction.code.toLowerCase().includes(query) ||
fraction.fraction.toLowerCase().includes(query) ||
(fraction.description ?? '').toLowerCase().includes(query) ||
(fraction.nico ?? '').toLowerCase().includes(query) ||
(fraction.umt ?? '').toLowerCase().includes(query)
);
}
});
async function loadTariffFractions(page: number = 1) {
const companyId = companyStore.activeCompany?.id;
if (!companyId) {
console.error('No hay compañía activa');
return;
}
isLoading = true;
try {
const response = await getTariffFractions(page, pageSize, companyId);
if (response.data) {
tariffFractions = response.data.items;
filteredFractions = response.data.items;
totalPages = response.data.pages;
totalRecords = response.data.total;
currentPage = response.data.page;
}
} catch (error) {
console.error('Error cargando fracciones arancelarias:', error);
} finally {
isLoading = false;
}
}
async function goToPage(page: number) {
if (page >= 1 && page <= totalPages && page !== currentPage) {
await loadTariffFractions(page);
}
}
</script>
<div class="container mx-auto p-6">
<Card.Root>
<Card.Header class="text-center border-b">
<Card.Title class="text-2xl font-bold uppercase">
Catálogo de Fracciones SITAR - SCAII
</Card.Title>
<p class="text-muted-foreground mt-2">
Nomenclatura arancelaria mexicana completa
</p>
</Card.Header>
<Card.Content class="pt-6">
<div class="space-y-6">
<!-- Barra de búsqueda y acciones -->
<div class="flex gap-4 items-center">
<div class="flex-1 relative">
<Search class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
bind:value={searchQuery}
placeholder="Buscar por código, fracción, descripción, NICO o UMT..."
class="pl-9"
/>
</div>
<Button variant="outline" size="icon" title="Exportar a CSV">
<Download class="h-4 w-4" />
</Button>
</div>
<!-- Información de registros -->
<div class="flex items-center justify-between text-sm text-muted-foreground">
<div>
{#if isLoading}
<div class="flex items-center gap-2">
<Loader2 class="h-4 w-4 animate-spin" />
<span>Cargando...</span>
</div>
{:else}
Mostrando {filteredFractions.length} de {totalRecords} fracciones arancelarias
{#if searchQuery}
(filtrado)
{/if}
{/if}
</div>
{#if !searchQuery && totalPages > 1}
<div class="flex items-center gap-2">
<span>Página {currentPage} de {totalPages}</span>
</div>
{/if}
</div>
<!-- Tabla de fracciones -->
<div class="border rounded-md overflow-auto max-h-[600px]">
<Table.Root>
<Table.Header class="sticky top-0 bg-background z-10">
<Table.Row>
<Table.Head class="w-[100px]">Código</Table.Head>
<Table.Head class="w-[120px]">Fracción</Table.Head>
<Table.Head class="min-w-[350px]">Descripción</Table.Head>
<Table.Head class="w-[80px]">NICO</Table.Head>
<Table.Head class="w-[80px]">UMT</Table.Head>
<Table.Head class="w-[100px]">Adv. Impo</Table.Head>
<Table.Head class="w-[100px]">Adv. Expo</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#if filteredFractions.length === 0}
<Table.Row>
<Table.Cell colspan={7} class="text-center py-8 text-muted-foreground">
{#if isLoading}
Cargando fracciones arancelarias...
{:else if searchQuery}
No se encontraron fracciones que coincidan con la búsqueda
{:else}
No hay fracciones arancelarias disponibles
{/if}
</Table.Cell>
</Table.Row>
{:else}
{#each filteredFractions as fraction (fraction.id)}
<Table.Row class="hover:bg-muted/50">
<Table.Cell class="font-mono text-sm">{fraction.code}</Table.Cell>
<Table.Cell class="font-mono text-sm font-medium">{fraction.fraction}</Table.Cell>
<Table.Cell class="text-sm">
{fraction.description || '-'}
</Table.Cell>
<Table.Cell class="text-sm">{fraction.nico || '-'}</Table.Cell>
<Table.Cell class="text-sm">{fraction.umt || '-'}</Table.Cell>
<Table.Cell class="text-sm">{fraction.adv_impo || '-'}</Table.Cell>
<Table.Cell class="text-sm">{fraction.adv_expo || '-'}</Table.Cell>
</Table.Row>
{/each}
{/if}
</Table.Body>
</Table.Root>
</div>
<!-- Paginación -->
{#if !searchQuery && totalPages > 1}
<div class="flex items-center justify-center gap-2">
<Button
variant="outline"
size="sm"
disabled={currentPage === 1 || isLoading}
onclick={() => goToPage(1)}
>
Primera
</Button>
<Button
variant="outline"
size="sm"
disabled={currentPage === 1 || isLoading}
onclick={() => goToPage(currentPage - 1)}
>
Anterior
</Button>
<span class="px-4 text-sm">
Página {currentPage} de {totalPages}
</span>
<Button
variant="outline"
size="sm"
disabled={currentPage === totalPages || isLoading}
onclick={() => goToPage(currentPage + 1)}
>
Siguiente
</Button>
<Button
variant="outline"
size="sm"
disabled={currentPage === totalPages || isLoading}
onclick={() => goToPage(totalPages)}
>
Última
</Button>
</div>
{/if}
</div>
</Card.Content>
</Card.Root>
</div>