Merge branch 'development' into feature/SSR-views

This commit is contained in:
2026-02-13 08:13:35 -06:00
123 changed files with 12052 additions and 1047 deletions

View File

@@ -49,7 +49,7 @@
name: '',
short_name: '',
curp: '',
client_or_provider: 'client',
client_or_provider: 'both',
type_nat_foreign: 'N',
responsible: '',
position: '',
@@ -224,7 +224,7 @@
} catch (e: any) {
console.error(e);
error = e.message || 'Error al guardar';
toast.error(error);
toast.error(error || 'Error al guardar');
} finally {
loading = false;
}
@@ -314,7 +314,11 @@
</div>
<div class="grid gap-2">
<Label for="type">Tipo de Relación <span class="text-destructive">*</span></Label>
<Select.Root type="single" bind:value={formData.client_or_provider}>
<Select.Root
type="single"
value={formData.client_or_provider}
onValueChange={(v) => (formData.client_or_provider = v)}
>
<Select.Trigger id="type">
{typeLabels[formData.client_or_provider] || 'Selecciona un tipo'}
</Select.Trigger>
@@ -357,7 +361,11 @@
</div>
<div class="grid gap-2">
<Label for="type_nat">Tipo Origen</Label>
<Select.Root type="single" bind:value={formData.type_nat_foreign}>
<Select.Root
type="single"
value={formData.type_nat_foreign}
onValueChange={(v) => (formData.type_nat_foreign = v)}
>
<Select.Trigger id="type_nat">
{formData.type_nat_foreign === 'N'
? 'Nacional'

View File

@@ -1,184 +0,0 @@
<script lang="ts">
import { onMount } from 'svelte';
import { partsApi, type Part } from '$lib/api/dashboard/a76/parts';
import DataTable from '$lib/components/dashboard/goods/parts/data-table.svelte';
import { createColumns } from '$lib/components/dashboard/goods/parts/columns.js';
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { companyStore } from '$lib/stores/company.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 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 (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({
company_id: companyId,
page: 1,
page_size: 100
});
if (response.data) {
partsList = response.data.items || [];
} else if (response.error) {
listError = response.error;
}
} catch (e) {
listError = 'Error de conexión con el servidor';
} finally {
listLoading = false;
}
}
// 3. REACTIVIDAD DE COMPAÑÍA
$effect(() => {
if (companyStore.activeCompany) {
loadParts();
}
});
// 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 {
const response = await partsApi.list({
company_id: companyId,
q: searchCode.trim()
});
const results = response.data?.items || [];
if (results.length > 0) {
searchResults = results; // Guardamos TODOS los resultados
isSearching = true; // Activamos modo búsqueda
} else {
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";
} finally {
searchLoading = false;
}
}
function clearSearch() {
searchCode = '';
searchResults = [];
isSearching = false;
listError = null;
loadParts(); // Recargamos la lista completa
}
const columns = createColumns(loadParts);
// Si estamos buscando, mostramos searchResults, si no, la lista completa
const tableData = $derived(isSearching ? searchResults : partsList);
</script>
<div class="space-y-6 p-4">
<div class="flex items-center justify-between">
<div>
<h1 class="text-3xl font-bold tracking-tight">Catálogo de Partes</h1>
<p class="text-muted-foreground">Gestiona las partes y componentes del sistema.</p>
</div>
<div class="flex gap-2">
<Button variant="outline" onclick={loadParts} disabled={listLoading}>
<RefreshCw class="mr-2 h-4 w-4 {listLoading ? 'animate-spin' : ''}" />
Actualizar
</Button>
<Button href="/dashboard/goods/parts/edit">
<Plus class="mr-2 h-4 w-4" />
Nueva Parte
</Button>
</div>
</div>
<Card.Root>
<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 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}>
{#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 isSearching || searchCode}
<Button variant="ghost" onclick={clearSearch}>
<Trash2 class="mr-2 h-4 w-4" />
Limpiar
</Button>
{/if}
</div>
</Card.Content>
</Card.Root>
<Card.Root>
<Card.Header>
<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 mb-4">
{listError}
</div>
{/if}
<DataTable
data={tableData}
{columns}
loading={listLoading || searchLoading}
hasMore={false}
loadMore={() => {}}
/>
</Card.Content>
</Card.Root>
</div>