175 lines
5.8 KiB
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>
|