feature/partidas-pais-origen

This commit is contained in:
2026-04-29 09:58:27 -06:00
parent d78163da12
commit 6d4e74015a
3 changed files with 106 additions and 43 deletions

View File

@@ -3,7 +3,7 @@
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Loader2, Search } from 'lucide-svelte';
import { countriesApi } from '$lib/api/dashboard/reference_data/countries';
import { countriesApi, type Country } from '$lib/api/dashboard/reference_data/countries';
import { companyStore } from '$lib/stores/company.svelte';
import { toast } from 'svelte-sonner';
@@ -12,17 +12,43 @@
onSelect
}: {
open: boolean;
onSelect: (country: any) => void;
onSelect: (country: Country) => void;
} = $props();
let countries: any[] = $state([]);
let filteredCountries: any[] = $state([]);
let countries = $state<Country[]>([]);
let loading = $state(false);
let loadingMore = $state(false);
let searchTerm = $state('');
let previousSearchTerm = '';
let page = $state(1);
const pageSize = 100;
let hasMore = $state(true);
let totalItems = $state(0);
let error = $state('');
let observer: IntersectionObserver | null = null;
let bottomSentinel: HTMLElement | null = $state(null);
let searchTimeout: ReturnType<typeof setTimeout> | undefined;
let isInitialized = false;
async function loadCountries() {
loading = true;
async function resetAndLoad() {
page = 1;
countries = [];
hasMore = true;
await loadCountries(true);
}
async function loadMore() {
if (!hasMore || loading || loadingMore) return;
page += 1;
await loadCountries(false);
}
async function loadCountries(isInitial: boolean) {
if (isInitial) {
loading = true;
} else {
loadingMore = true;
}
error = '';
try {
const companyId = companyStore.activeCompany?.id;
@@ -31,13 +57,17 @@
return;
}
// Para este diálogo, cargamos una cantidad grande o implementamos paginación si fuera necesario
// Por ahora seguimos el patrón original de cargar "todos" (limite 100 en backend)
const response = await countriesApi.list(companyId, 1, 100);
const response = await countriesApi.list(companyId, page, pageSize, searchTerm);
if (response.data) {
countries = response.data.items || [];
filteredCountries = countries;
const newItems = response.data.items || [];
totalItems = response.data.total || 0;
if (isInitial) {
countries = newItems;
} else {
countries = [...countries, ...newItems];
}
hasMore = countries.length < totalItems && newItems.length > 0;
} else if (response.error) {
error = `Error: ${response.error}`;
toast.error(error);
@@ -48,30 +78,16 @@
toast.error(error);
} finally {
loading = false;
loadingMore = false;
}
}
function filterCountries() {
if (!searchTerm.trim()) {
filteredCountries = countries;
} else {
const term = searchTerm.toLowerCase();
filteredCountries = countries.filter(
(country) =>
country.m3_key?.toLowerCase().includes(term) ||
country.mex_key?.toLowerCase().includes(term) ||
country.description_es?.toLowerCase().includes(term) ||
country.description_en?.toLowerCase().includes(term)
);
}
}
function handleSelect(country: any) {
function handleSelect(country: Country) {
onSelect(country);
open = false;
}
function handleRowKeydown(event: KeyboardEvent, country: any) {
function handleRowKeydown(event: KeyboardEvent, country: Country) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
handleSelect(country);
@@ -79,13 +95,45 @@
}
$effect(() => {
if (open) {
loadCountries();
if (open && !isInitialized) {
isInitialized = true;
previousSearchTerm = searchTerm;
void resetAndLoad();
} else if (!open) {
isInitialized = false;
}
});
$effect(() => {
filterCountries();
const term = searchTerm;
if (isInitialized && term !== previousSearchTerm) {
if (searchTimeout) clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
previousSearchTerm = term;
void resetAndLoad();
}, 400);
}
});
$effect(() => {
if (bottomSentinel && hasMore && !loading && !loadingMore && open) {
if (observer) observer.disconnect();
observer = new IntersectionObserver(
(entries) => {
if (entries[0]?.isIntersecting && hasMore && !loading && !loadingMore) {
void loadMore();
}
},
{ threshold: 0.1 }
);
observer.observe(bottomSentinel);
}
return () => {
if (observer) observer.disconnect();
};
});
</script>
@@ -100,14 +148,19 @@
<Search class="w-4 h-4 text-zinc-400" />
<Input
bind:value={searchTerm}
placeholder="Buscando..."
placeholder="Buscar por clave M3, MEX, AME o descripción..."
class="flex-1 h-9"
type="search"
/>
</div>
<p class="mt-2 text-xs text-zinc-500">
{totalItems > 0 ? `${countries.length} de ${totalItems} mostrados` : ''} — desplázate para cargar
más; el catálogo completo supera 100 países.
</p>
</div>
<div class="flex-1 overflow-auto px-6 py-4">
{#if loading}
{#if loading && countries.length === 0}
<div class="flex items-center justify-center py-20">
<Loader2 class="w-8 h-8 animate-spin text-zinc-900 dark:text-zinc-100" />
</div>
@@ -136,21 +189,21 @@
</tr>
</thead>
<tbody>
{#each filteredCountries as country}
{#each countries as country}
<tr
class="border-b hover:bg-zinc-100 dark:hover:bg-zinc-800 cursor-pointer transition-colors"
tabindex="0"
onclick={() => handleSelect(country)}
onkeydown={(event) => handleRowKeydown(event, country)}
>
<td class="px-3 py-2 border-r">{country.m3_key || ''}</td>
<td class="px-3 py-2 border-r">{country.mex_key || ''}</td>
<td class="px-3 py-2 border-r">{country.description_es || ''}</td>
<td class="px-3 py-2 border-r text-center">{country.ame_key || ''}</td>
<td class="px-3 py-2">{country.description_en || ''}</td>
<td class="px-3 py-2 border-r">{country.m3_key || ''}</td>
<td class="px-3 py-2 border-r">{country.mex_key || ''}</td>
<td class="px-3 py-2 border-r">{country.description_es || ''}</td>
<td class="px-3 py-2 border-r text-center">{country.ame_key || ''}</td>
<td class="px-3 py-2">{country.description_en || ''}</td>
</tr>
{/each}
{#if filteredCountries.length === 0}
{#if countries.length === 0}
<tr>
<td colspan="5" class="px-3 py-8 text-center text-zinc-500">
No se encontraron resultados
@@ -161,6 +214,16 @@
</table>
</div>
<div
bind:this={bottomSentinel}
class="h-10 flex items-center justify-center"
aria-hidden="true"
>
{#if loadingMore}
<Loader2 class="h-5 w-5 animate-spin text-zinc-500" />
{/if}
</div>
<div class="mt-4 text-sm text-zinc-600 dark:text-zinc-400">
Usa flechas para navegar filas y Enter/Espacio para seleccionar.
</div>

View File

@@ -178,7 +178,7 @@
function handleCountrySelect(country: any) {
customs.origin_country = country.m3_key || country.mex_key;
(customs as any).origin_country_name = country.description || country.description_en;
(customs as any).origin_country_name = country.description_es || country.description_en;
}
function handleFractionSelect(fraction: any) {

View File

@@ -113,7 +113,7 @@
if (!editingItem.customs) editingItem.customs = {} as any;
if (editingItem.customs) {
editingItem.customs.origin_country = country.m3_key || country.mex_key;
(editingItem.customs as any).origin_country_name = country.description || country.description_en;
(editingItem.customs as any).origin_country_name = country.description_es || country.description_en;
}
}