Inputs de busqueda

This commit is contained in:
2026-02-11 14:18:18 -06:00
parent 6e276cd8a9
commit c26a064d1c
2 changed files with 132 additions and 26 deletions

View File

@@ -61,6 +61,9 @@ class DodaService:
if filters.get("patent"):
query = query.filter(
Doda.patent.ilike(f"%{filters['patent']}%"))
if filters.get("operation_type"):
query = query.filter(
Doda.operation_type == filters["operation_type"])
total = query.count()
dodas = query.offset(skip).limit(limit).all()

View File

@@ -16,6 +16,7 @@
LayoutGrid,
Printer
} from 'lucide-svelte';
import * as Select from '$lib/components/ui/select';
import { companyStore } from '$lib/stores/company.svelte';
import DataTable from '$lib/components/dashboard/general_catalogs/doda/data-table.svelte';
import { createColumns } from '$lib/components/dashboard/general_catalogs/doda/columns';
@@ -30,8 +31,14 @@
let { data } = $props();
let dialogOpen = $state(false);
// Filtros
let searchIntegration = $state($page.url.searchParams.get('integration_number') || '');
// Filtros centralizados
let filters = $state({
integration_number: $page.url.searchParams.get('integration_number') || '',
patent: $page.url.searchParams.get('patent') || '',
status: $page.url.searchParams.get('status') || '',
operation_type: $page.url.searchParams.get('operation_type') || ''
});
let timeout: ReturnType<typeof setTimeout>;
// State for infinite scroll
@@ -47,7 +54,7 @@
let selectedId = $state<number | null>(null);
const selectedDoda = $derived(selectedId ? allItems.find((i) => i.id === selectedId) : null);
// Sincronizar con datos del servidor al cargar
// Sincronizar con datos del servidor al cargar (primera carga)
$effect(() => {
if (data.dodas) {
allItems = data.dodas.items || [];
@@ -56,6 +63,35 @@
}
});
// Sincronizar filtros con la URL de forma reactiva
$effect(() => {
if (browser) {
const params = new URLSearchParams();
if (filters.integration_number) params.set('integration_number', filters.integration_number);
if (filters.patent) params.set('patent', filters.patent);
if (filters.status) params.set('status', filters.status);
if (filters.operation_type) params.set('operation_type', filters.operation_type);
const queryString = params.toString();
const newUrl = queryString ? `?${queryString}` : window.location.pathname;
if (window.location.search !== (queryString ? `?${queryString}` : '')) {
window.history.replaceState({}, '', newUrl);
}
}
});
// Disparar recarga cuando cambian los filtros (con debounce)
$effect(() => {
// Observamos todos los campos de filtros
const _ = { ...filters };
clearTimeout(timeout);
timeout = setTimeout(() => {
reloadData();
}, 400);
});
// Atajos
useShortcuts(
'Lista DODA',
@@ -73,8 +109,12 @@
const companyId = companyStore.activeCompany?.id;
if (!companyId) return;
const filters = searchIntegration ? { integration_number: searchIntegration } : {};
const res = await getDodas(currentPage + 1, pageSize, filters, Number(companyId));
// Limpiar filtros vacíos
const activeFilters = Object.fromEntries(
Object.entries(filters).filter(([_, v]) => v !== '')
);
const res = await getDodas(currentPage + 1, pageSize, activeFilters, Number(companyId));
if (res.data) {
allItems = [...allItems, ...res.data.items];
@@ -89,13 +129,17 @@
}
async function reloadData() {
if (!browser) return;
loading = true;
try {
const companyId = companyStore.activeCompany?.id;
if (!companyId) return;
const filters = searchIntegration ? { integration_number: searchIntegration } : {};
const res = await getDodas(1, pageSize, filters, Number(companyId));
const activeFilters = Object.fromEntries(
Object.entries(filters).filter(([_, v]) => v !== '')
);
const res = await getDodas(1, pageSize, activeFilters, Number(companyId));
if (res.data) {
allItems = res.data.items;
@@ -105,17 +149,20 @@
}
} catch (e) {
console.error('Error reloading DODAs:', e);
toast.error('Error al recargar datos');
// Evitar mostrar error si es solo carga inicial y falla por falta de login etc
if (allItems.length > 0) toast.error('Error al recargar datos');
} finally {
loading = false;
}
}
function handleSearch() {
clearTimeout(timeout);
timeout = setTimeout(() => {
reloadData();
}, 500);
function clearFilters() {
filters = {
integration_number: '',
patent: '',
status: '',
operation_type: ''
};
}
function handleCreateClick() {
@@ -161,21 +208,77 @@
<Card.Root>
<Card.Header>
<Card.Title>Filtros</Card.Title>
<Card.Description>Búsqueda rápida por número de integración</Card.Description>
<div class="flex items-center justify-between">
<div>
<Card.Title>Filtros Avanzados</Card.Title>
<Card.Description>Refina tu búsqueda mediante múltiples criterios</Card.Description>
</div>
<Button variant="ghost" size="sm" onclick={clearFilters} class="text-muted-foreground">
<RotateCcw class="mr-2 h-4 w-4" />
Limpiar Filtros
</Button>
</div>
</Card.Header>
<Card.Content>
<div class="max-w-sm space-y-2">
<Label for="search-integration">No. Integración</Label>
<div class="relative">
<Search class="absolute top-2.5 left-2.5 h-4 w-4 text-muted-foreground" />
<Input
id="search-integration"
placeholder="Escribe para buscar..."
bind:value={searchIntegration}
oninput={handleSearch}
class="pl-9"
/>
<div class="grid grid-cols-1 gap-4 md:grid-cols-4">
<div class="space-y-2">
<Label for="search-integration">No. Integración</Label>
<div class="relative">
<Search class="absolute top-2.5 left-2.5 h-4 w-4 text-muted-foreground" />
<Input
id="search-integration"
placeholder="Buscar integración..."
bind:value={filters.integration_number}
class="pl-9"
/>
</div>
</div>
<div class="space-y-2">
<Label for="search-patent">Patente</Label>
<Input id="search-patent" placeholder="Buscar patente..." bind:value={filters.patent} />
</div>
<div class="space-y-2">
<Label>Estatus</Label>
<Select.Root
type="single"
value={filters.status}
onValueChange={(v) => (filters.status = v)}
>
<Select.Trigger class="w-full">
{filters.status || 'Todos los estatus'}
</Select.Trigger>
<Select.Content>
<Select.Item value="">Todos</Select.Item>
<Select.Item value="PENDIENTE">PENDIENTE</Select.Item>
<Select.Item value="GENERADO">GENERADO</Select.Item>
<Select.Item value="VALIDADO">VALIDADO</Select.Item>
<Select.Item value="ELIMINADO">ELIMINADO</Select.Item>
</Select.Content>
</Select.Root>
</div>
<div class="space-y-2">
<Label>Operación</Label>
<Select.Root
type="single"
value={filters.operation_type}
onValueChange={(v) => (filters.operation_type = v)}
>
<Select.Trigger class="w-full">
{filters.operation_type === 'I'
? 'Importación'
: filters.operation_type === 'E'
? 'Exportación'
: 'Todas'}
</Select.Trigger>
<Select.Content>
<Select.Item value="">Todas</Select.Item>
<Select.Item value="I">I - Importación</Select.Item>
<Select.Item value="E">E - Exportación</Select.Item>
</Select.Content>
</Select.Root>
</div>
</div>
</Card.Content>