// frontend/src/components/GestionComponentes.tsx import { useState, useEffect, useMemo, useCallback } from 'react'; import toast from 'react-hot-toast'; import { useReactTable, getCoreRowModel, getFilteredRowModel, getSortedRowModel, flexRender, type SortingState, } from '@tanstack/react-table'; import styles from './SimpleTable.module.css'; import { adminService } from '../services/apiService'; // Interfaces para los diferentes tipos de datos interface TextValue { valor: string; conteo: number; } interface RamValue { fabricante?: string; tamano: number; velocidad?: number; conteo: number; } type ComponentValue = TextValue | RamValue; const GestionComponentes = () => { const [componentType, setComponentType] = useState('os'); const [valores, setValores] = useState([]); const [isLoading, setIsLoading] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false); const [valorAntiguo, setValorAntiguo] = useState(''); const [valorNuevo, setValorNuevo] = useState(''); // Estados para la tabla (filtrado y ordenamiento) const [globalFilter, setGlobalFilter] = useState(''); const [sorting, setSorting] = useState([]); useEffect(() => { setIsLoading(true); adminService.getComponentValues(componentType) .then(data => { setValores(data); }) .catch(_err => { toast.error(`No se pudieron cargar los datos de ${componentType}.`); }) .finally(() => setIsLoading(false)); }, [componentType]); const handleOpenModal = useCallback((valor: string) => { setValorAntiguo(valor); setValorNuevo(valor); setIsModalOpen(true); }, []); const handleUnificar = async () => { const toastId = toast.loading('Unificando valores...'); try { await adminService.unifyComponentValues(componentType, valorAntiguo, valorNuevo); const refreshedData = await adminService.getComponentValues(componentType); setValores(refreshedData); toast.success('Valores unificados correctamente.', { id: toastId }); setIsModalOpen(false); } catch (error) { if (error instanceof Error) toast.error(error.message, { id: toastId }); } }; const handleDeleteRam = useCallback(async (ramGroup: RamValue) => { if (!window.confirm("¿Estás seguro de eliminar todas las entradas maestras para este tipo de RAM? Esta acción es irreversible.")) { return; } const toastId = toast.loading('Eliminando grupo de módulos...'); try { await adminService.deleteRamComponent({ fabricante: ramGroup.fabricante, tamano: ramGroup.tamano, velocidad: ramGroup.velocidad }); setValores(prev => prev.filter(v => { const currentRam = v as RamValue; return !(currentRam.fabricante === ramGroup.fabricante && currentRam.tamano === ramGroup.tamano && currentRam.velocidad === ramGroup.velocidad); })); toast.success("Grupo de módulos de RAM eliminado.", { id: toastId }); } catch (error) { if (error instanceof Error) toast.error(error.message, { id: toastId }); } }, []); const handleDeleteTexto = useCallback(async (valor: string) => { if (!window.confirm(`Este valor ya no está en uso. ¿Quieres eliminarlo de la base de datos maestra?`)) { return; } const toastId = toast.loading('Eliminando valor...'); try { await adminService.deleteTextComponent(componentType, valor); setValores(prev => prev.filter(v => (v as TextValue).valor !== valor)); toast.success("Valor eliminado.", { id: toastId }); } catch (error) { if (error instanceof Error) toast.error(error.message, { id: toastId }); } }, [componentType]); const renderValor = useCallback((item: ComponentValue) => { if (componentType === 'ram') { const ram = item as RamValue; return `${ram.fabricante || 'Desconocido'} ${ram.tamano}GB ${ram.velocidad ? ram.velocidad + 'MHz' : ''}`; } return (item as TextValue).valor; }, [componentType]); const columns = useMemo(() => [ { header: 'Valor Registrado', id: 'valor', accessorFn: (row: ComponentValue) => renderValor(row), }, { header: 'Nº de Equipos', accessorKey: 'conteo', }, { header: 'Acciones', id: 'acciones', cell: ({ row }: { row: { original: ComponentValue } }) => { const item = row.original; return (
{componentType === 'ram' ? ( ) : ( <> )}
); } } ], [componentType, renderValor, handleDeleteRam, handleDeleteTexto, handleOpenModal]); const table = useReactTable({ data: valores, columns, state: { sorting, globalFilter, }, onSortingChange: setSorting, onGlobalFilterChange: setGlobalFilter, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), }); return (

Gestión de Componentes Maestros ({table.getFilteredRowModel().rows.length})

Unifica valores inconsistentes y elimina registros no utilizados.

** La Tabla permite ordenar por multiple columnas manteniendo shift al hacer click en la cabecera. **

setGlobalFilter(e.target.value)} className={styles.searchInput} style={{ width: '300px' }} />
{isLoading ? (

Cargando...

) : (
{table.getHeaderGroups().map(headerGroup => ( {headerGroup.headers.map(header => ( ))} ))} {table.getRowModel().rows.map(row => ( {row.getVisibleCells().map(cell => ( ))} ))}
{flexRender(header.column.columnDef.header, header.getContext())} {header.column.getIsSorted() && ( {header.column.getIsSorted() === 'asc' ? '⇑' : '⇓'} )}
{flexRender(cell.column.columnDef.cell, cell.getContext())}
)} {isModalOpen && (

Unificar Valor

Se reemplazarán todas las instancias de:

{valorAntiguo} setValorNuevo(e.target.value)} className={styles.modalInput} />
)}
); }; export default GestionComponentes;