Files
plantillas-proyectos/frontend/src/lib/components/dashboard/goods/modales/TariffFractionSelector.svelte

175 lines
5.8 KiB
Svelte

<script lang="ts">
import * as Dialog from '$lib/components/ui/dialog';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import { Button } from '$lib/components/ui/button';
import { Loader2, Search } from 'lucide-svelte';
import {
getTariffFractions,
type TariffFraction
} from '$lib/api/dashboard/a76/general_catalogs/tariff-fractions';
import { companyStore } from '$lib/stores/company.svelte';
let {
open = $bindable(false),
onSelect
}: { open: boolean; onSelect: (fraction: TariffFraction) => void } = $props();
let tariffFractions = $state<TariffFraction[]>([]);
let searchFraction = $state('');
let currentPage = $state(1);
let totalFractions = $state(0);
let hasMoreFractions = $state(true);
let isLoadingFractions = $state(false);
let searchTimeout: ReturnType<typeof setTimeout>;
// Cargar datos al abrir
let wasOpen = $state(false);
// Cargar datos al abrir
$effect(() => {
if (open && !wasOpen) {
// Reiniciar estado SOLO al abrir
searchFraction = '';
currentPage = 1;
totalFractions = 0;
hasMoreFractions = true;
tariffFractions = [];
loadFractions('', 1);
}
wasOpen = open;
});
async function loadFractions(search: string, page: number = currentPage) {
const companyId = companyStore.activeCompany?.id;
if (!companyId || isLoadingFractions) return;
isLoadingFractions = true;
try {
const filters = search ? { search } : {};
const pageSize = 100;
const response = await getTariffFractions(page, pageSize, companyId, filters);
if (response.data) {
if (page === 1) {
tariffFractions = [...response.data.items];
} else {
tariffFractions = [...tariffFractions, ...response.data.items];
}
totalFractions = response.data.total;
currentPage = page;
hasMoreFractions = tariffFractions.length < response.data.total;
}
} catch (error) {
console.error('Error cargando fracciones arancelarias:', error);
} finally {
isLoadingFractions = false;
}
}
</script>
<Dialog.Root bind:open>
<Dialog.Content class="w-full max-w-[95vw] sm:max-w-7xl">
<Dialog.Header>
<Dialog.Title>CATALOGO DE FRACCIONES SITAR - SCAII</Dialog.Title>
</Dialog.Header>
<div class="space-y-4">
<div>
<Label for="search_fraction">Buscando:</Label>
<div class="relative mt-1">
<Search class="absolute top-2.5 left-2 h-4 w-4 text-muted-foreground" />
<Input
id="search_fraction"
bind:value={searchFraction}
placeholder="Buscar por fracción, descripción, NICO..."
class="pl-8"
oninput={() => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
tariffFractions = [];
currentPage = 1;
totalFractions = 0;
hasMoreFractions = true;
loadFractions(searchFraction, 1);
}, 500);
}}
/>
</div>
</div>
<div
class="max-h-[600px] overflow-x-auto overflow-y-auto rounded-md border"
onscroll={(e) => {
const target = e.currentTarget;
if (
target.scrollHeight - target.scrollTop <= target.clientHeight + 50 &&
hasMoreFractions &&
!isLoadingFractions
) {
loadFractions(searchFraction, currentPage + 1);
}
}}
>
<table class="w-full">
<thead class="border-b bg-white text-gray-900 dark:bg-black dark:text-white">
<tr>
<th class="px-4 py-2 text-left font-semibold whitespace-nowrap">Clave</th>
<th class="px-4 py-2 text-left font-semibold whitespace-nowrap">Fracción</th>
<th class="px-4 py-2 text-left font-semibold whitespace-nowrap">NICO</th>
<th class="px-4 py-2 text-left font-semibold whitespace-nowrap">Descripción</th>
<th class="px-4 py-2 text-left font-semibold whitespace-nowrap">U.M.T</th>
<th class="px-4 py-2 text-left font-semibold whitespace-nowrap">Adv. Impo</th>
<th class="px-4 py-2 text-left font-semibold whitespace-nowrap">Adv. Expo</th>
<th class="px-4 py-2 text-left font-semibold whitespace-nowrap">DOF</th>
<th class="px-4 py-2 text-left font-semibold whitespace-nowrap">Aplica IEPS</th>
</tr>
</thead>
<tbody>
{#each tariffFractions as fraction (fraction.id)}
<tr
class="cursor-pointer border-b transition-colors hover:bg-gray-100 dark:hover:bg-gray-700"
onclick={() => {
onSelect(fraction);
open = false;
}}
>
<td class="px-4 py-2 font-mono whitespace-nowrap">{fraction.um_code}</td>
<td class="px-4 py-2 whitespace-nowrap">{fraction.fraction}</td>
<td class="px-4 py-2 whitespace-nowrap">{fraction.nico}</td>
<td class="px-4 py-2">{fraction.description}</td>
<td class="px-4 py-2 whitespace-nowrap">{fraction.umt}</td>
<td class="px-4 py-2 whitespace-nowrap">{fraction.adv_impo}</td>
<td class="px-4 py-2 whitespace-nowrap">{fraction.adv_expo}</td>
<td class="px-4 py-2 whitespace-nowrap">{fraction.dof}</td>
<td class="px-4 py-2 whitespace-nowrap">{fraction.aplica_ieps}</td>
</tr>
{:else}
<tr>
<td colspan="9" class="px-4 py-8 text-center text-muted-foreground">
{#if isLoadingFractions}
<div class="flex items-center justify-center gap-2">
<Loader2 class="h-4 w-4 animate-spin" />
Cargando fracciones...
</div>
{:else}
No hay fracciones disponibles
{/if}
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{#if isLoadingFractions && tariffFractions.length > 0}
<div class="flex justify-center py-2">
<Loader2 class="h-4 w-4 animate-spin text-muted-foreground" />
</div>
{/if}
</div>
<div class="flex justify-end">
<Button variant="outline" onclick={() => (open = false)}>Cancelar</Button>
</div>
</Dialog.Content>
</Dialog.Root>