Files
Inventario-IT/frontend/src/components/AutocompleteInput.tsx
dmolinari 268c1c2bf9 Mejoras integrales en UI, lógica de negocio y auditoría
Este commit introduce una serie de mejoras significativas en toda la aplicación, abordando la experiencia de usuario, la consistencia de los datos, la robustez del backend y la implementación de un historial de cambios completo.

 **Funcionalidades y Mejoras (Features & Enhancements)**

*   **Historial de Auditoría Completo:**
    *   Se implementa el registro en el historial para todas las acciones CRUD manuales: creación de equipos, adición y eliminación de discos, RAM y usuarios.
    *   Los cambios de campos simples (IP, Hostname, etc.) ahora también se registran detalladamente.

*   **Consistencia de Datos Mejorada:**
    *   **RAM:** La selección de RAM en el modal de "Añadir RAM" y la vista de "Administración" ahora agrupan los módulos por especificaciones (Fabricante, Tamaño, Velocidad), eliminando las entradas duplicadas causadas por diferentes `part_number`.
    *   **Arquitectura:** El campo de edición para la arquitectura del equipo se ha cambiado de un input de texto a un selector con las opciones fijas "32 bits" y "64 bits".

*   **Experiencia de Usuario (UX) Optimizada:**
    *   El botón de "Wake On Lan" (WOL) ahora se deshabilita visualmente si el equipo no tiene una dirección MAC registrada.
    *   Se corrige el apilamiento de modales: los sub-modales (Añadir Disco/RAM/Usuario) ahora siempre aparecen por encima del modal principal de detalles y bloquean su cierre.
    *   El historial de cambios se actualiza en tiempo real en la interfaz después de añadir o eliminar un componente, sin necesidad de cerrar y reabrir el modal.

🐛 **Correcciones (Bug Fixes)**

*   **Actualización de Estado en Vivo:** Al añadir/eliminar un módulo de RAM, los campos "RAM Instalada" y "Última Actualización" ahora se recalculan en el backend y se actualizan instantáneamente en el frontend.
*   **Historial de Sectores Legible:** Se corrige el registro del historial para que al cambiar un sector se guarde el *nombre* del sector (ej. "Técnica") en lugar de su ID numérico.
*   **Formulario de Edición:** El dropdown de "Sector" en el modo de edición ahora preselecciona correctamente el sector asignado actualmente al equipo.
*   **Error Crítico al Añadir RAM:** Se soluciona un error del servidor (`Sequence contains more than one element`) que ocurría al añadir manualmente un tipo de RAM que ya existía con múltiples `part_number`. Se reemplazó `QuerySingleOrDefaultAsync` por `QueryFirstOrDefaultAsync` para mayor robustez.
*   **Eliminación Segura:** Se impide la eliminación de un sector si este tiene equipos asignados, protegiendo la integridad de los datos.

♻️ **Refactorización (Refactoring)**

*   **Servicio de API Centralizado:** Toda la lógica de llamadas `fetch` del frontend ha sido extraída de los componentes y centralizada en un único servicio (`apiService.ts`), mejorando drásticamente la mantenibilidad y organización del código.
*   **Optimización de Renders:** Se ha optimizado el rendimiento de los modales mediante el uso del hook `useCallback` para memorizar funciones que se pasan como props.
*   **Nulabilidad en C#:** Se han resuelto múltiples advertencias de compilación (`CS8620`) en el backend al especificar explícitamente los tipos de referencia anulables (`string?`), mejorando la seguridad de tipos del código.
2025-10-08 13:27:44 -03:00

75 lines
2.3 KiB
TypeScript

import React, { useState, useEffect } from 'react';
// --- Interfaces de Props más robustas usando una unión discriminada ---
type AutocompleteInputProps = {
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
name: string;
placeholder?: string;
className?: string;
} & ( // Esto crea una unión: o es estático o es dinámico
| {
mode: 'static';
fetchSuggestions: () => Promise<string[]>; // No necesita 'query'
}
| {
mode: 'dynamic';
fetchSuggestions: (query: string) => Promise<string[]>; // Necesita 'query'
}
);
const AutocompleteInput: React.FC<AutocompleteInputProps> = (props) => {
const { value, onChange, name, placeholder, className } = props;
const [suggestions, setSuggestions] = useState<string[]>([]);
const dataListId = `suggestions-for-${name}`;
// --- Lógica para el modo ESTÁTICO ---
// Se ejecuta UNA SOLA VEZ cuando el componente se monta
useEffect(() => {
if (props.mode === 'static') {
props.fetchSuggestions()
.then(setSuggestions)
.catch(err => console.error(`Error fetching static suggestions for ${name}:`, err));
}
// La lista de dependencias asegura que solo se ejecute si estas props cambian (lo cual no harán)
}, [props.mode, props.fetchSuggestions, name]);
// --- Lógica para el modo DINÁMICO ---
// Se ejecuta cada vez que el usuario escribe, con un debounce
useEffect(() => {
if (props.mode === 'dynamic') {
if (value.length < 2) {
setSuggestions([]);
return;
}
const handler = setTimeout(() => {
props.fetchSuggestions(value)
.then(setSuggestions)
.catch(err => console.error(`Error fetching dynamic suggestions for ${name}:`, err));
}, 300);
return () => clearTimeout(handler);
}
}, [value, props.mode, props.fetchSuggestions, name]);
return (
<>
<input
type="text"
name={name}
value={value}
onChange={onChange}
placeholder={placeholder}
className={className}
list={dataListId}
autoComplete="off"
/>
<datalist id={dataListId}>
{suggestions.map((suggestion, index) => (
<option key={index} value={suggestion} />
))}
</datalist>
</>
);
};
export default AutocompleteInput;