From 9bdb401e78e0717413f63a55eca3c0627625076a Mon Sep 17 00:00:00 2001 From: hreyes Date: Fri, 24 Apr 2026 08:33:27 -0600 Subject: [PATCH] feature/filtros-cliente-proveedor --- .../a76/clients_and_providers/routes.py | 23 ++++++++-- frontend/src/lib/api/help.ts | 5 ++- .../clients_and_providers/+page.svelte | 43 ++++++++++++------- 3 files changed, 49 insertions(+), 22 deletions(-) diff --git a/backend/api/v1/modules/a76/clients_and_providers/routes.py b/backend/api/v1/modules/a76/clients_and_providers/routes.py index bd3ad62c..0c012924 100644 --- a/backend/api/v1/modules/a76/clients_and_providers/routes.py +++ b/backend/api/v1/modules/a76/clients_and_providers/routes.py @@ -7,6 +7,7 @@ from typing import List, Optional from core.database import get_core_db from core.security import get_current_user, validate_access_to_resource from fastapi import APIRouter, Depends, HTTPException, Query +from sqlalchemy import or_ from sqlalchemy.orm import Session, joinedload from api.v1.common.tenant_crud_routes import TenantCRUDRoutes from .models import ClientOrProviderEnum @@ -32,10 +33,16 @@ router.include_router(imports_router, prefix="/imports", tags=["clients_and_prov @router.get("/", response_model=ClientProviderPaginatedResponseDTO) async def get_clients_and_providers( company_id: int = Query(..., description="Company ID"), + name: Optional[str] = Query(None, description="Filter by name (contains)"), + rfc: Optional[str] = Query(None, description="Filter by RFC/TAX-ID (contains)"), type: Optional[ClientOrProviderEnum] = Query( None, description="Type of entity (client or provider)" ), active: Optional[bool] = Query(None, description="Active status"), + page: Optional[int] = Query(None, ge=1, description="Page number (1-based)"), + page_size: Optional[int] = Query( + None, ge=1, le=1000, description="Page size when using page-based pagination" + ), skip: int = Query(0, ge=0), limit: int = Query(100, ge=1, le=1000), db: Session = Depends(get_core_db), @@ -44,6 +51,9 @@ async def get_clients_and_providers( """Get clients and providers""" tenant_id = validate_access_to_resource(db, company_id, current_user) + resolved_limit = page_size if page_size is not None else limit + resolved_skip = ((page - 1) * resolved_limit) if page is not None else skip + query = db.query(ClientProvider).options( joinedload(ClientProvider.address), joinedload(ClientProvider.programs) @@ -52,9 +62,14 @@ async def get_clients_and_providers( ClientProvider.company_id == company_id, ) + if name: + query = query.filter(ClientProvider.name.ilike(f"%{name.strip()}%")) + + if rfc: + query = query.filter(ClientProvider.rfc.ilike(f"%{rfc.strip()}%")) + if type is not None: # Include 'both' type when filtering by client or provider - from sqlalchemy import or_ query = query.filter( or_( ClientProvider.client_or_provider == type, @@ -66,13 +81,13 @@ async def get_clients_and_providers( query = query.filter(ClientProvider.is_active == active) total = query.count() - clients = query.offset(skip).limit(limit).all() + clients = query.offset(resolved_skip).limit(resolved_limit).all() return { "items": [ClientProviderResponseDTO.model_validate(c) for c in clients], "total": total, - "page": (skip // limit) + 1, - "page_size": limit, + "page": (resolved_skip // resolved_limit) + 1, + "page_size": resolved_limit, } @router.get("/{client_id}/basic", response_model=ClientProviderBasicDTO) diff --git a/frontend/src/lib/api/help.ts b/frontend/src/lib/api/help.ts index 70693704..e4907ccf 100644 --- a/frontend/src/lib/api/help.ts +++ b/frontend/src/lib/api/help.ts @@ -1,8 +1,9 @@ import { getToken, authStore } from '$lib/auth'; import { get } from 'svelte/store'; -const api_url = import.meta.env.VITE_API_URL; -const BASE_URL = `${api_url.endsWith('/') ? api_url : api_url + '/'}v1/core/help-center`; +const api_url = import.meta.env.VITE_API_URL ?? ''; +const normalizedApiUrl = api_url ? (api_url.endsWith('/') ? api_url : `${api_url}/`) : '/'; +const BASE_URL = `${normalizedApiUrl}v1/core/help-center`; function getAuthToken(): string | null { // 1. First try getToken() which checks Keycloak and localStorage diff --git a/frontend/src/routes/dashboard/clients_and_providers/+page.svelte b/frontend/src/routes/dashboard/clients_and_providers/+page.svelte index 758f1fb7..df7541e1 100644 --- a/frontend/src/routes/dashboard/clients_and_providers/+page.svelte +++ b/frontend/src/routes/dashboard/clients_and_providers/+page.svelte @@ -26,17 +26,18 @@ let items = $state(data.items || []); let selectedItem = $state(null); let isLoading = $state(false); - let hasMore = $derived(items.length < totalItems); // Server-side filtering/pagination parameters let currentPage = $state(data.page || 1); let pageSize = $state(50); let totalItems = $state(data.total || 0); + let hasMore = $derived(items.length < totalItems); // Filter state let searchName = $state(''); let searchRfc = $state(''); let searchType = $state($page.url.searchParams.get('type') || 'both'); + let filterDebounce: ReturnType | null = null; // Estado para el diálogo de crear let showCreateDialog = $state(false); @@ -75,8 +76,10 @@ try { const filters: any = {}; if (searchType !== 'both') filters.type = searchType; - if (searchName) filters.name = searchName; - if (searchRfc) filters.rfc = searchRfc; + const trimmedName = searchName.trim(); + const trimmedRfc = searchRfc.trim(); + if (trimmedName) filters.name = trimmedName; + if (trimmedRfc) filters.rfc = trimmedRfc; const response = await clientsProvidersApi.list(companyId, pageToLoad, pageSize, filters); @@ -114,11 +117,6 @@ function handleTypeChange(value: string) { searchType = value; - loadItems(1); - } - - function handleSearch() { - loadItems(1); } function selectItem(item: ClientProvider) { @@ -162,6 +160,26 @@ recargar: () => loadItems(1) }) ); + + $effect(() => { + const companyId = companyStore.activeCompany?.id; + if (!browser || !companyId) return; + + searchName; + searchRfc; + searchType; + pageSize; + + if (filterDebounce) clearTimeout(filterDebounce); + filterDebounce = setTimeout(() => { + selectedItem = null; + loadItems(1); + }, 350); + + return () => { + if (filterDebounce) clearTimeout(filterDebounce); + }; + });
@@ -197,14 +215,13 @@

Filtros

Busque por nombre, RFC/TAX-ID o tipo
-
+
e.key === 'Enter' && handleSearch()} />
@@ -213,7 +230,6 @@ bind:value={searchRfc} placeholder="RFC / TAX-ID..." class="h-9" - onkeydown={(e) => e.key === 'Enter' && handleSearch()} />
@@ -233,11 +249,6 @@
-
- -