Se agrego el titulo de busqueda en partes

This commit is contained in:
2026-01-07 13:17:54 -06:00
parent adbaa0fd18
commit cddd64d730
3 changed files with 59 additions and 37 deletions

View File

@@ -22,13 +22,9 @@
type A76ClassCreate,
type A76ClassUpdate
} from '$lib/api/dashboard/a76/classes';
// APIs para recuperar nombres al editar
import { materialTypesApi } from "$lib/api/dashboard/a76/material-types";
import { clientsProvidersApi } from '$lib/api/dashboard/a76/clients-providers';
// Nota: Asumo que tienes una API para obtener una unidad por código o lista,
// si no, usaremos la descripción del modal.
// --- MODALES IMPORTADOS ---
import MaterialTypeSelectorDialog from '$lib/components/dashboard/goods/modales/material-type-selector-dialog.svelte';
import UnitMeasureSelectorDialog from '$lib/components/dashboard/goods/modales/unit-measure-dialog.svelte';
@@ -43,12 +39,10 @@
let loading = $state(false);
let error = $state<string | null>(null);
// Estados de Modales
let showClientModal = $state(false);
let showMaterialModal = $state(false);
let showUnitModal = $state(false);
// Descripciones Visuales (Para que el usuario sepa qué seleccionó)
let selectedClientName = $state("");
let selectedMaterialDesc = $state("");
let selectedUnitDesc = $state("");
@@ -62,7 +56,7 @@
description_es: '',
description_en: '',
material_key: '',
unit_of_measure: '', // Ahora vacío para obligar selección
unit_of_measure: '',
fraction: '',
us_fraction: '',
sub_key: '',
@@ -83,7 +77,7 @@
}
});
// Carga del Registro a Editar y sus descripciones visuales
async function loadClassData(classId: number, companyId: number) {
loading = true;
try {
@@ -111,10 +105,9 @@
iva_exempt_fraction: data.iva_exempt_fraction
};
// Recuperar descripciones visuales para que no se vean solo códigos
if (data.client_id) await fetchClientName(data.client_id, companyId);
if (data.material_key) await fetchMaterialName(data.material_key);
if (data.unit_of_measure) selectedUnitDesc = data.unit_of_measure; // O buscar descripción si tienes API
if (data.unit_of_measure) selectedUnitDesc = data.unit_of_measure;
}
} catch (e) {
error = "No se pudo cargar la información de la Clase";
@@ -135,8 +128,6 @@
async function fetchMaterialName(key: string) {
try {
// Asumiendo que list devuelve items y podemos buscar ahí,
// o si tienes un get(key) mejor.
const res = await materialTypesApi.list(1, 100);
const list = (res as any).data?.items || [];
const found = list.find((m: any) => m.key === key);
@@ -158,7 +149,6 @@
}
function handleUnitSelect(item: any) {
// Asumiendo que el modal devuelve { code: 'KG', description: 'Kilogramos' }
formData.unit_of_measure = item.code;
selectedUnitDesc = item.description || item.code;
}

View File

@@ -6,26 +6,29 @@
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import { companyStore } from '$lib/stores/company.svelte';
import { Plus, Search, Trash2, RefreshCw } from 'lucide-svelte';
import { Plus, Search, RefreshCw, Loader2, Trash2 } from 'lucide-svelte';
// 1. ESTADOS
let partsList = $state<Part[]>([]);
let listLoading = $state(false);
let listError = $state<string | null>(null);
// Estado Búsqueda
let searchCode = $state('');
let searchedPart = $state<Part | null>(null);
let searchResults = $state<Part[]>([]); // Array para guardar los resultados
let searchLoading = $state(false);
let isSearching = $state(false); // Para saber si estamos viendo resultados de búsqueda
// 2. CARGA DE DATOS
// 2. CARGA DE DATOS (Lista Completa)
async function loadParts() {
const companyId = companyStore.activeCompany?.id;
if (!companyId) return;
listLoading = true;
listError = null;
isSearching = false; // Reseteamos modo búsqueda
searchResults = [];
try {
const response = await partsApi.list({
@@ -35,10 +38,7 @@
});
if (response.data) {
partsList = response.data.items || response.data.parts || [];
console.log("Datos recibidos:", response.data);
} else if (response.error) {
listError = response.error;
}
@@ -56,10 +56,14 @@
}
});
// 4. BÚSQUEDA MANUAL
async function handleSearch() {
if (!searchCode.trim()) return;
searchLoading = true;
listError = null; // Limpiamos errores previos
const companyId = companyStore.activeCompany?.id;
if (!companyId) return;
try {
@@ -71,9 +75,12 @@
const results = response.data.items || response.data.parts || [];
if (results.length > 0) {
searchedPart = results[0];
searchResults = results; // Guardamos TODOS los resultados
isSearching = true; // Activamos modo búsqueda
} else {
listError = "No se encontró la parte";
listError = "No se encontraron partes con ese criterio";
searchResults = [];
isSearching = true; // Aún en modo búsqueda, pero vacía
}
} catch (e) {
listError = "Error en la búsqueda";
@@ -84,12 +91,16 @@
function clearSearch() {
searchCode = '';
searchedPart = null;
loadParts();
searchResults = [];
isSearching = false;
listError = null;
loadParts(); // Recargamos la lista completa
}
const columns = createColumns(loadParts);
const tableData = $derived(searchedPart ? [searchedPart] : partsList);
// Si estamos buscando, mostramos searchResults, si no, la lista completa
const tableData = $derived(isSearching ? searchResults : partsList);
</script>
<div class="space-y-6 p-4">
@@ -111,17 +122,32 @@
</div>
<Card.Root>
<Card.Content class="pt-6">
<Card.Header>
<Card.Title>Búsqueda Rápida</Card.Title>
<Card.Description>Ingresa el número de parte o descripción para filtrar.</Card.Description>
</Card.Header>
<Card.Content>
<div class="flex gap-4">
<div class="flex-1">
<Input bind:value={searchCode} placeholder="Buscar por número de parte o descripción..." />
<div class="flex-1 max-w-xl">
<Input
bind:value={searchCode}
placeholder="Buscar por número de parte o descripción..."
onkeydown={(e) => e.key === 'Enter' && handleSearch()}
/>
</div>
<Button onclick={handleSearch} disabled={searchLoading}>
<Search class="mr-2 h-4 w-4" />
Buscar
{#if searchLoading}
<Loader2 class="mr-2 h-4 w-4 animate-spin" />
Buscando...
{:else}
<Search class="mr-2 h-4 w-4" />
Buscar
{/if}
</Button>
{#if searchedPart || searchCode}
{#if isSearching || searchCode}
<Button variant="ghost" onclick={clearSearch}>
<Trash2 class="mr-2 h-4 w-4" />
Limpiar
</Button>
{/if}
@@ -131,16 +157,22 @@
<Card.Root>
<Card.Header>
<Card.Title>{searchedPart ? 'Resultado' : 'Listado General'}</Card.Title>
<Card.Title>
{#if isSearching}
Resultados de la búsqueda ({searchResults.length})
{:else}
Listado General ({partsList.length} registros)
{/if}
</Card.Title>
</Card.Header>
<Card.Content>
{#if listError}
<div class="bg-destructive/10 text-destructive p-4 rounded-lg border border-destructive/20">
<div class="bg-destructive/10 text-destructive p-4 rounded-lg border border-destructive/20 mb-4">
{listError}
</div>
{:else}
<DataTable data={tableData} {columns} />
{/if}
<DataTable data={tableData} {columns} />
</Card.Content>
</Card.Root>
</div>

View File

@@ -545,7 +545,7 @@
placeholder="Seleccione un cliente..."
readonly
onclick={() => showClientModal = true}
/>
/>
</div>
<Button variant="outline" class="shrink-0" type="button" onclick={() => showClientModal = true}>
<Search class="h-4 w-4 mr-2" /> Buscar