diff --git a/backend/api/v1/modules/a76/classes/service.py b/backend/api/v1/modules/a76/classes/service.py index 49c06bcb..acd7e717 100644 --- a/backend/api/v1/modules/a76/classes/service.py +++ b/backend/api/v1/modules/a76/classes/service.py @@ -48,27 +48,30 @@ class ClassService: query = query.filter(Class.company_id == company_id) if filters: - if filters.get("q"): - search = f"%{filters['q']}%" + # Búsqueda libre: OR en clave y descripciones (selectores / listados) + search_raw = filters.get("search") or filters.get("q") + if search_raw and str(search_raw).strip(): + pattern = f"%{str(search_raw).strip()}%" query = query.filter( or_( - Class.class_code.ilike(search), - Class.description_es.ilike(search), - Class.description_en.ilike(search) + Class.class_code.ilike(pattern), + Class.description_es.ilike(pattern), + Class.description_en.ilike(pattern), ) ) - if filters.get("class_code"): - query = query.filter( - Class.class_code.ilike(f"%{filters['class_code']}%") - ) - if filters.get("description"): - description_pattern = f"%{filters['description']}%" - query = query.filter( - or_( - Class.description_es.ilike(description_pattern), - Class.description_en.ilike(description_pattern), + else: + if filters.get("class_code"): + query = query.filter( + Class.class_code.ilike(f"%{filters['class_code']}%") + ) + if filters.get("description"): + description_pattern = f"%{filters['description']}%" + query = query.filter( + or_( + Class.description_es.ilike(description_pattern), + Class.description_en.ilike(description_pattern), + ) ) - ) if filters.get("material_key"): query = query.filter( Class.material_key.ilike(f"%{filters['material_key']}%") diff --git a/frontend/src/lib/api/dashboard/a76/classes.ts b/frontend/src/lib/api/dashboard/a76/classes.ts index 00c1cc4a..6f0eb86b 100644 --- a/frontend/src/lib/api/dashboard/a76/classes.ts +++ b/frontend/src/lib/api/dashboard/a76/classes.ts @@ -58,7 +58,9 @@ export interface A76ClassListParams { page_size?: number; class_code?: string; description?: string; - q?: string; // Agregado por si usas búsqueda general + /** Búsqueda OR en class_code, description_es y description_en (backend ClassService) */ + search?: string; + q?: string; sort_by?: string; sort_order?: 'asc' | 'desc'; } diff --git a/frontend/src/lib/components/dashboard/goods/parts/class-selector-dialog.svelte b/frontend/src/lib/components/dashboard/goods/parts/class-selector-dialog.svelte index 7a954a0a..cfa9e69c 100644 --- a/frontend/src/lib/components/dashboard/goods/parts/class-selector-dialog.svelte +++ b/frontend/src/lib/components/dashboard/goods/parts/class-selector-dialog.svelte @@ -1,156 +1,220 @@ - - - Seleccionar Clase (Anexo 24) - - Seleccione la clasificación del material. - - + + + Seleccionar Clase (Anexo 24) + + Seleccione la clasificación del material. Desplace hacia abajo para cargar más resultados. + + -
- - -
+
+ + +
-
- {#if loading} -
- -

Cargando catálogo...

-
- {:else if filteredItems.length === 0} -
-

No se encontraron clases.

-
- {:else} - - - - - - - - - - {#each filteredItems as item} - handleSelect(item)} - > - - - +
+ {#if loading} +
+ +

Cargando catálogo...

+
+ {:else if items.length === 0} +
+

No se encontraron clases.

+
+ {:else} +
ClaveDescripciónUM
- - {item.class_code} - - -
- - - {item.description_es || 'Sin descripción'} - -
-
+ + + + + + + + + {#each items as item} + handleSelect(item)} + > + - - - {/each} - -
ClaveDescripciónUM
+ + {item.class_code} + + -
- - {item.unit_of_measure || '-'} -
-
- {/if} -
+ +
+ + + {item.description_es || item.description_en || 'Sin descripción'} + +
+ - -
- {filteredItems.length} registros encontrados -
- -
-
-
\ No newline at end of file + +
+ + {item.unit_of_measure || '-'} +
+ + + {/each} + + + {#if loadingMore} +
+ +
+ {/if} + {/if} + + + +
+ {#if loading} + … + {:else} + Mostrando {items.length} de {total} registros + {/if} +
+ +
+ + diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/class-dialog.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/class-dialog.svelte index 71a07c9e..3d091ec5 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/class-dialog.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/class-dialog.svelte @@ -7,6 +7,7 @@ import { toast } from 'svelte-sonner'; import { companyStore } from '$lib/stores/company.svelte'; import { onMount } from 'svelte'; + import { classesApi } from '$lib/api/dashboard/a76/classes'; let { open = $bindable(false), @@ -22,9 +23,9 @@ let isLoadingMore = $state(false); let classes = $state([]); let currentPage = $state(1); - let hasMore = $state(true); let totalItems = $state(0); - const itemsPerPage = 25; + const itemsPerPage = 50; + const hasMore = $derived(classes.length < totalItems && totalItems > 0); async function fetchClasses(page: number = 1, search: string = '') { const activeCompanyId = companyStore?.activeCompany?.id; @@ -34,26 +35,17 @@ else isLoadingMore = true; try { - // Construct query params - const params = new URLSearchParams({ - company_id: activeCompanyId.toString(), - page: page.toString(), - page_size: itemsPerPage.toString(), - sort_by: 'class_code', - sort_order: 'asc' + const res: any = await classesApi.list({ + company_id: activeCompanyId, + page, + page_size: itemsPerPage, + ...(search ? { search } : {}) }); - - if (search) { - // Most TenantCRUDRoutes support 'q' for general search or field-specific filters - params.append('q', search); - } - - const response = await fetch(`/api-sveltekit/classes?${params.toString()}`); - - if (!response.ok) throw new Error('Error al buscar clases'); - - const data = await response.json(); - const newItems = data.items || []; + + // Manejar diferentes formatos de respuesta + const data = res.data ?? res; + const newItems = Array.isArray(data.items) ? data.items : []; + const total = data.total ?? (page === 1 ? newItems.length : classes.length + newItems.length); if (page === 1) { classes = newItems; @@ -61,12 +53,15 @@ classes = [...classes, ...newItems]; } - totalItems = data.total || 0; - hasMore = newItems.length === itemsPerPage; + totalItems = total; currentPage = page; } catch (error) { console.error('Error fetching classes:', error); toast.error('Error al cargar clases'); + if (page === 1) { + classes = []; + totalItems = 0; + } } finally { isSearching = false; isLoadingMore = false; @@ -75,9 +70,7 @@ // Debounce effect $effect(() => { - // Accedemos a searchQuery para que el efecto dependa de él const query = searchQuery; - const timeout = setTimeout(() => { if (debouncedSearch !== query) { debouncedSearch = query; @@ -85,10 +78,20 @@ fetchClasses(1, query); } }, 400); - return () => clearTimeout(timeout); }); + $effect(() => { + if (!open) { + searchQuery = ''; + classes = []; + totalItems = 0; + currentPage = 1; + } + }); + + + function handleSelect(classItem: any) { if (onSelect) onSelect(classItem); open = false; @@ -158,7 +161,7 @@ - {#each classes as classItem (classItem.id)} + {#each classes as classItem (classItem.id || classItem.class_code)} handleSelect(classItem)} @@ -197,7 +200,6 @@ {/if} {/each} - {#if hasMore}