feat: Mejora reactividad de la UI y funcionalidad de tablas
- **Corrección de Sincronización de Estado:** Se soluciona un problema crítico donde las modificaciones de datos (ej. cambiar una contraseña o eliminar una asociación) no se reflejaban visualmente en la tabla hasta que se recargaba la página. Se ha refactorizado el manejo del estado para garantizar que todos los componentes se actualicen instantáneamente después de una acción. - **Actualización Global de Contraseñas:** Se mejora la lógica de actualización de contraseñas. Ahora, al cambiar la clave de un usuario, el cambio se refleja en **todos** los equipos a los que dicho usuario está asociado en la tabla, no solo en la fila desde donde se inició la acción. - **Mejoras en Gestión de Componentes:** - Se implementa la librería `@tanstack/react-table` en el componente `GestionComponentes.tsx`. - La tabla de "Administración" ahora cuenta con filtrado global de registros y ordenamiento por columnas, mejorando su usabilidad y manteniendo consistencia con la tabla principal de equipos. - **Corrección de Tipos en TypeScript:** Se resuelven errores de tipo (`Property 'id' does not exist on type 'never'`) en la definición de las columnas de la tabla (`SimpleTable.tsx`) mediante el tipado explícito con `CellContext`, mejorando la robustez y la experiencia de desarrollo.
This commit is contained in:
@@ -1,6 +1,14 @@
|
|||||||
// frontend/src/components/GestionComponentes.tsx
|
// frontend/src/components/GestionComponentes.tsx
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
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 styles from './SimpleTable.module.css';
|
||||||
import { adminService } from '../services/apiService';
|
import { adminService } from '../services/apiService';
|
||||||
|
|
||||||
@@ -17,14 +25,20 @@ interface RamValue {
|
|||||||
conteo: number;
|
conteo: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ComponentValue = TextValue | RamValue;
|
||||||
|
|
||||||
const GestionComponentes = () => {
|
const GestionComponentes = () => {
|
||||||
const [componentType, setComponentType] = useState('os');
|
const [componentType, setComponentType] = useState('os');
|
||||||
const [valores, setValores] = useState<(TextValue | RamValue)[]>([]);
|
const [valores, setValores] = useState<ComponentValue[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const [valorAntiguo, setValorAntiguo] = useState('');
|
const [valorAntiguo, setValorAntiguo] = useState('');
|
||||||
const [valorNuevo, setValorNuevo] = useState('');
|
const [valorNuevo, setValorNuevo] = useState('');
|
||||||
|
|
||||||
|
// Estados para la tabla (filtrado y ordenamiento)
|
||||||
|
const [globalFilter, setGlobalFilter] = useState('');
|
||||||
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
adminService.getComponentValues(componentType)
|
adminService.getComponentValues(componentType)
|
||||||
@@ -37,11 +51,11 @@ const GestionComponentes = () => {
|
|||||||
.finally(() => setIsLoading(false));
|
.finally(() => setIsLoading(false));
|
||||||
}, [componentType]);
|
}, [componentType]);
|
||||||
|
|
||||||
const handleOpenModal = (valor: string) => {
|
const handleOpenModal = useCallback((valor: string) => {
|
||||||
setValorAntiguo(valor);
|
setValorAntiguo(valor);
|
||||||
setValorNuevo(valor);
|
setValorNuevo(valor);
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const handleUnificar = async () => {
|
const handleUnificar = async () => {
|
||||||
const toastId = toast.loading('Unificando valores...');
|
const toastId = toast.loading('Unificando valores...');
|
||||||
@@ -56,15 +70,13 @@ const GestionComponentes = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 2. FUNCIÓN DELETE ACTUALIZADA: Ahora maneja un grupo
|
const handleDeleteRam = useCallback(async (ramGroup: RamValue) => {
|
||||||
const handleDeleteRam = 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.")) {
|
if (!window.confirm("¿Estás seguro de eliminar todas las entradas maestras para este tipo de RAM? Esta acción es irreversible.")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const toastId = toast.loading('Eliminando grupo de módulos...');
|
const toastId = toast.loading('Eliminando grupo de módulos...');
|
||||||
try {
|
try {
|
||||||
// El servicio ahora espera el objeto del grupo
|
|
||||||
await adminService.deleteRamComponent({
|
await adminService.deleteRamComponent({
|
||||||
fabricante: ramGroup.fabricante,
|
fabricante: ramGroup.fabricante,
|
||||||
tamano: ramGroup.tamano,
|
tamano: ramGroup.tamano,
|
||||||
@@ -81,9 +93,9 @@ const GestionComponentes = () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) toast.error(error.message, { id: toastId });
|
if (error instanceof Error) toast.error(error.message, { id: toastId });
|
||||||
}
|
}
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const handleDeleteTexto = async (valor: string) => {
|
const handleDeleteTexto = useCallback(async (valor: string) => {
|
||||||
if (!window.confirm(`Este valor ya no está en uso. ¿Quieres eliminarlo de la base de datos maestra?`)) {
|
if (!window.confirm(`Este valor ya no está en uso. ¿Quieres eliminarlo de la base de datos maestra?`)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -95,49 +107,32 @@ const GestionComponentes = () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) toast.error(error.message, { id: toastId });
|
if (error instanceof Error) toast.error(error.message, { id: toastId });
|
||||||
}
|
}
|
||||||
};
|
}, [componentType]);
|
||||||
|
|
||||||
const renderValor = (item: TextValue | RamValue) => {
|
const renderValor = useCallback((item: ComponentValue) => {
|
||||||
if (componentType === 'ram') {
|
if (componentType === 'ram') {
|
||||||
const ram = item as RamValue;
|
const ram = item as RamValue;
|
||||||
return `${ram.fabricante || 'Desconocido'} ${ram.tamano}GB ${ram.velocidad ? ram.velocidad + 'MHz' : ''}`;
|
return `${ram.fabricante || 'Desconocido'} ${ram.tamano}GB ${ram.velocidad ? ram.velocidad + 'MHz' : ''}`;
|
||||||
}
|
}
|
||||||
return (item as TextValue).valor;
|
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 (
|
return (
|
||||||
<div>
|
|
||||||
<h2>Gestión de Componentes Maestros</h2>
|
|
||||||
<p>Unifica valores inconsistentes y elimina registros no utilizados.</p>
|
|
||||||
|
|
||||||
<div style={{ marginBottom: '1.5rem' }}>
|
|
||||||
<label><strong>Selecciona un tipo de componente:</strong></label>
|
|
||||||
<select value={componentType} onChange={e => setComponentType(e.target.value)} className={styles.sectorSelect} style={{marginLeft: '10px'}}>
|
|
||||||
<option value="os">Sistema Operativo</option>
|
|
||||||
<option value="cpu">CPU</option>
|
|
||||||
<option value="motherboard">Motherboard</option>
|
|
||||||
<option value="architecture">Arquitectura</option>
|
|
||||||
<option value="ram">Memorias RAM</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isLoading ? (
|
|
||||||
<p>Cargando...</p>
|
|
||||||
) : (
|
|
||||||
<table className={styles.table}>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th className={styles.th}>Valor Registrado</th>
|
|
||||||
<th className={styles.th} style={{width: '150px'}}>Nº de Equipos</th>
|
|
||||||
<th className={styles.th} style={{width: '200px'}}>Acciones</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{valores.map((item) => (
|
|
||||||
<tr key={componentType === 'ram' ? `${(item as RamValue).fabricante}-${(item as RamValue).tamano}-${(item as RamValue).velocidad}` : (item as TextValue).valor} className={styles.tr}>
|
|
||||||
<td className={styles.td}>{renderValor(item)}</td>
|
|
||||||
<td className={styles.td}>{item.conteo}</td>
|
|
||||||
<td className={styles.td}>
|
|
||||||
<div style={{ display: 'flex', gap: '5px' }}>
|
<div style={{ display: 'flex', gap: '5px' }}>
|
||||||
{componentType === 'ram' ? (
|
{componentType === 'ram' ? (
|
||||||
<button
|
<button
|
||||||
@@ -166,11 +161,84 @@ const GestionComponentes = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
], [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 (
|
||||||
|
<div>
|
||||||
|
<h2>Gestión de Componentes Maestros ({table.getFilteredRowModel().rows.length})</h2>
|
||||||
|
<p>Unifica valores inconsistentes y elimina registros no utilizados.</p>
|
||||||
|
<p style={{ fontSize: '14px', fontWeight: 'bold' }}>** La Tabla permite ordenar por multiple columnas manteniendo shift al hacer click en la cabecera. **</p>
|
||||||
|
|
||||||
|
<div className={styles.controlsContainer}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Filtrar registros..."
|
||||||
|
value={globalFilter}
|
||||||
|
onChange={(e) => setGlobalFilter(e.target.value)}
|
||||||
|
className={styles.searchInput}
|
||||||
|
style={{ width: '300px' }}
|
||||||
|
/>
|
||||||
|
<label><strong>Tipo de componente:</strong></label>
|
||||||
|
<select value={componentType} onChange={e => setComponentType(e.target.value)} className={styles.sectorSelect}>
|
||||||
|
<option value="os">Sistema Operativo</option>
|
||||||
|
<option value="cpu">CPU</option>
|
||||||
|
<option value="motherboard">Motherboard</option>
|
||||||
|
<option value="architecture">Arquitectura</option>
|
||||||
|
<option value="ram">Memorias RAM</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isLoading ? (
|
||||||
|
<p>Cargando...</p>
|
||||||
|
) : (
|
||||||
|
<div style={{ overflowX: 'auto', border: '1px solid #dee2e6', borderRadius: '8px', marginTop: '1rem' }}>
|
||||||
|
<table className={styles.table}>
|
||||||
|
<thead>
|
||||||
|
{table.getHeaderGroups().map(headerGroup => (
|
||||||
|
<tr key={headerGroup.id}>
|
||||||
|
{headerGroup.headers.map(header => (
|
||||||
|
<th key={header.id} className={styles.th} onClick={header.column.getToggleSortingHandler()}>
|
||||||
|
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||||
|
{header.column.getIsSorted() && (
|
||||||
|
<span className={styles.sortIndicator}>
|
||||||
|
{header.column.getIsSorted() === 'asc' ? '⇑' : '⇓'}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{table.getRowModel().rows.map(row => (
|
||||||
|
<tr key={row.id} className={styles.tr}>
|
||||||
|
{row.getVisibleCells().map(cell => (
|
||||||
|
<td key={cell.id} className={styles.td}>
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
</td>
|
</td>
|
||||||
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isModalOpen && (
|
{isModalOpen && (
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
useReactTable, getCoreRowModel, getFilteredRowModel, getSortedRowModel,
|
useReactTable, getCoreRowModel, getFilteredRowModel, getSortedRowModel,
|
||||||
getPaginationRowModel, flexRender, type CellContext
|
getPaginationRowModel, flexRender, type CellContext,
|
||||||
|
type ColumnDef
|
||||||
} from '@tanstack/react-table';
|
} from '@tanstack/react-table';
|
||||||
import { Tooltip } from 'react-tooltip';
|
import { Tooltip } from 'react-tooltip';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
@@ -125,13 +126,18 @@ const SimpleTable = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
if (!modalData || !modalData.sector) return;
|
if (!modalData) return;
|
||||||
const toastId = toast.loading('Guardando...');
|
const toastId = toast.loading('Guardando...');
|
||||||
try {
|
try {
|
||||||
await equipoService.updateSector(modalData.id, modalData.sector.id);
|
const sectorId = modalData.sector?.id ?? 0;
|
||||||
const updatedData = data.map(e => e.id === modalData.id ? { ...e, sector: modalData.sector } : e);
|
await equipoService.updateSector(modalData.id, sectorId);
|
||||||
setData(updatedData);
|
const equipoActualizado = { ...modalData, sector_id: modalData.sector?.id };
|
||||||
setFilteredData(updatedData);
|
const updateFunc = (prev: Equipo[]) => prev.map(e => e.id === modalData.id ? equipoActualizado : e);
|
||||||
|
setData(updateFunc);
|
||||||
|
setFilteredData(updateFunc);
|
||||||
|
if (selectedEquipo && selectedEquipo.id === modalData.id) {
|
||||||
|
setSelectedEquipo(equipoActualizado);
|
||||||
|
}
|
||||||
toast.success('Sector actualizado.', { id: toastId });
|
toast.success('Sector actualizado.', { id: toastId });
|
||||||
setModalData(null);
|
setModalData(null);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -143,28 +149,57 @@ const SimpleTable = () => {
|
|||||||
if (!modalPasswordData) return;
|
if (!modalPasswordData) return;
|
||||||
const toastId = toast.loading('Actualizando...');
|
const toastId = toast.loading('Actualizando...');
|
||||||
try {
|
try {
|
||||||
const updatedUser = await usuarioService.updatePassword(modalPasswordData.id, password);
|
await usuarioService.updatePassword(modalPasswordData.id, password);
|
||||||
const updatedData = data.map(equipo => ({
|
|
||||||
...equipo,
|
const usernameToUpdate = modalPasswordData.username;
|
||||||
usuarios: equipo.usuarios?.map(user => user.id === updatedUser.id ? { ...user, password } : user)
|
|
||||||
}));
|
const newData = data.map(equipo => {
|
||||||
setData(updatedData);
|
if (!equipo.usuarios.some(u => u.username === usernameToUpdate)) {
|
||||||
setFilteredData(updatedData);
|
return equipo;
|
||||||
toast.success(`Contraseña actualizada.`, { id: toastId });
|
}
|
||||||
|
const updatedUsers = equipo.usuarios.map(user =>
|
||||||
|
user.username === usernameToUpdate ? { ...user, password: password } : user
|
||||||
|
);
|
||||||
|
return { ...equipo, usuarios: updatedUsers };
|
||||||
|
});
|
||||||
|
setData(newData);
|
||||||
|
if (selectedSector === 'Todos') setFilteredData(newData);
|
||||||
|
else if (selectedSector === 'Asignar') setFilteredData(newData.filter(i => !i.sector));
|
||||||
|
else setFilteredData(newData.filter(i => i.sector?.nombre === selectedSector));
|
||||||
|
if (selectedEquipo) {
|
||||||
|
const updatedSelectedEquipo = newData.find(e => e.id === selectedEquipo.id);
|
||||||
|
if (updatedSelectedEquipo) {
|
||||||
|
setSelectedEquipo(updatedSelectedEquipo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success(`Contraseña para '${usernameToUpdate}' actualizada en todos sus equipos.`, { id: toastId });
|
||||||
setModalPasswordData(null);
|
setModalPasswordData(null);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) toast.error(error.message, { id: toastId });
|
if (error instanceof Error) toast.error(error.message, { id: toastId });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const handleRemoveUser = async (hostname: string, username: string) => {
|
const handleRemoveUser = async (hostname: string, username: string) => {
|
||||||
if (!window.confirm(`¿Quitar a ${username} de este equipo?`)) return;
|
if (!window.confirm(`¿Quitar a ${username} de este equipo?`)) return;
|
||||||
const toastId = toast.loading(`Quitando a ${username}...`);
|
const toastId = toast.loading(`Quitando a ${username}...`);
|
||||||
try {
|
try {
|
||||||
await usuarioService.removeUserFromEquipo(hostname, username);
|
await usuarioService.removeUserFromEquipo(hostname, username);
|
||||||
const updateFunc = (prev: Equipo[]) => prev.map(e => e.hostname === hostname ? { ...e, usuarios: e.usuarios.filter(u => u.username !== username) } : e);
|
let equipoActualizado: Equipo | undefined;
|
||||||
|
const updateFunc = (prev: Equipo[]) => prev.map(e => {
|
||||||
|
if (e.hostname === hostname) {
|
||||||
|
equipoActualizado = { ...e, usuarios: e.usuarios.filter(u => u.username !== username) };
|
||||||
|
return equipoActualizado;
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
});
|
||||||
setData(updateFunc);
|
setData(updateFunc);
|
||||||
setFilteredData(updateFunc);
|
setFilteredData(updateFunc);
|
||||||
|
if (selectedEquipo && equipoActualizado && selectedEquipo.id === equipoActualizado.id) {
|
||||||
|
setSelectedEquipo(equipoActualizado);
|
||||||
|
}
|
||||||
toast.success(`${username} quitado.`, { id: toastId });
|
toast.success(`${username} quitado.`, { id: toastId });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) toast.error(error.message, { id: toastId });
|
if (error instanceof Error) toast.error(error.message, { id: toastId });
|
||||||
@@ -188,7 +223,6 @@ const SimpleTable = () => {
|
|||||||
|
|
||||||
const handleRemoveAssociation = async (type: 'disco' | 'ram' | 'usuario', associationId: number | { equipoId: number, usuarioId: number }) => {
|
const handleRemoveAssociation = async (type: 'disco' | 'ram' | 'usuario', associationId: number | { equipoId: number, usuarioId: number }) => {
|
||||||
if (!window.confirm('¿Estás seguro de que quieres eliminar esta asociación manual?')) return;
|
if (!window.confirm('¿Estás seguro de que quieres eliminar esta asociación manual?')) return;
|
||||||
|
|
||||||
const toastId = toast.loading('Eliminando asociación...');
|
const toastId = toast.loading('Eliminando asociación...');
|
||||||
try {
|
try {
|
||||||
let successMessage = '';
|
let successMessage = '';
|
||||||
@@ -204,7 +238,6 @@ const SimpleTable = () => {
|
|||||||
} else {
|
} else {
|
||||||
throw new Error('Tipo de asociación no válido');
|
throw new Error('Tipo de asociación no válido');
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateState = (prevEquipos: Equipo[]) => prevEquipos.map(equipo => {
|
const updateState = (prevEquipos: Equipo[]) => prevEquipos.map(equipo => {
|
||||||
if (equipo.id !== selectedEquipo?.id) return equipo;
|
if (equipo.id !== selectedEquipo?.id) return equipo;
|
||||||
let updatedEquipo = { ...equipo };
|
let updatedEquipo = { ...equipo };
|
||||||
@@ -217,11 +250,9 @@ const SimpleTable = () => {
|
|||||||
}
|
}
|
||||||
return updatedEquipo;
|
return updatedEquipo;
|
||||||
});
|
});
|
||||||
|
|
||||||
setData(updateState);
|
setData(updateState);
|
||||||
setFilteredData(updateState);
|
setFilteredData(updateState);
|
||||||
setSelectedEquipo(prev => prev ? updateState([prev])[0] : null);
|
setSelectedEquipo(prev => prev ? updateState([prev])[0] : null);
|
||||||
|
|
||||||
if (selectedEquipo) {
|
if (selectedEquipo) {
|
||||||
await refreshHistory(selectedEquipo.hostname);
|
await refreshHistory(selectedEquipo.hostname);
|
||||||
}
|
}
|
||||||
@@ -248,13 +279,10 @@ const SimpleTable = () => {
|
|||||||
const toastId = toast.loading('Guardando cambios...');
|
const toastId = toast.loading('Guardando cambios...');
|
||||||
try {
|
try {
|
||||||
const equipoActualizadoDesdeBackend = await equipoService.updateManual(id, equipoEditado);
|
const equipoActualizadoDesdeBackend = await equipoService.updateManual(id, equipoEditado);
|
||||||
const updateState = (prev: Equipo[]) =>
|
const updateState = (prev: Equipo[]) => prev.map(e => e.id === id ? equipoActualizadoDesdeBackend : e);
|
||||||
prev.map(e => e.id === id ? equipoActualizadoDesdeBackend : e);
|
|
||||||
|
|
||||||
setData(updateState);
|
setData(updateState);
|
||||||
setFilteredData(updateState);
|
setFilteredData(updateState);
|
||||||
setSelectedEquipo(equipoActualizadoDesdeBackend);
|
setSelectedEquipo(equipoActualizadoDesdeBackend);
|
||||||
|
|
||||||
toast.success('Equipo actualizado.', { id: toastId });
|
toast.success('Equipo actualizado.', { id: toastId });
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -275,18 +303,13 @@ const SimpleTable = () => {
|
|||||||
default: throw new Error('Tipo de componente no válido');
|
default: throw new Error('Tipo de componente no válido');
|
||||||
}
|
}
|
||||||
await serviceCall;
|
await serviceCall;
|
||||||
|
|
||||||
// Usar el servicio directamente para obtener el equipo actualizado
|
|
||||||
const refreshedEquipo = await equipoService.getAll().then(equipos => equipos.find(e => e.id === selectedEquipo.id));
|
const refreshedEquipo = await equipoService.getAll().then(equipos => equipos.find(e => e.id === selectedEquipo.id));
|
||||||
if (!refreshedEquipo) throw new Error("No se pudo recargar el equipo");
|
if (!refreshedEquipo) throw new Error("No se pudo recargar el equipo");
|
||||||
|
|
||||||
const updateState = (prev: Equipo[]) => prev.map(e => e.id === selectedEquipo.id ? refreshedEquipo : e);
|
const updateState = (prev: Equipo[]) => prev.map(e => e.id === selectedEquipo.id ? refreshedEquipo : e);
|
||||||
setData(updateState);
|
setData(updateState);
|
||||||
setFilteredData(updateState);
|
setFilteredData(updateState);
|
||||||
setSelectedEquipo(refreshedEquipo);
|
setSelectedEquipo(refreshedEquipo);
|
||||||
|
|
||||||
await refreshHistory(selectedEquipo.hostname);
|
await refreshHistory(selectedEquipo.hostname);
|
||||||
|
|
||||||
toast.success(`${type.charAt(0).toUpperCase() + type.slice(1)} añadido.`, { id: toastId });
|
toast.success(`${type.charAt(0).toUpperCase() + type.slice(1)} añadido.`, { id: toastId });
|
||||||
setAddingComponent(null);
|
setAddingComponent(null);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -294,11 +317,12 @@ const SimpleTable = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns = [
|
// --- DEFINICIÓN DE COLUMNAS CORREGIDA ---
|
||||||
|
const columns: ColumnDef<Equipo>[] = [
|
||||||
{ header: "ID", accessorKey: "id", enableHiding: true },
|
{ header: "ID", accessorKey: "id", enableHiding: true },
|
||||||
{
|
{
|
||||||
header: "Nombre", accessorKey: "hostname",
|
header: "Nombre", accessorKey: "hostname",
|
||||||
cell: ({ row }: CellContext<Equipo, any>) => (<button onClick={() => setSelectedEquipo(row.original)} className={styles.hostnameButton}>{row.original.hostname}</button>)
|
cell: (info: CellContext<Equipo, any>) => (<button onClick={() => setSelectedEquipo(info.row.original)} className={styles.hostnameButton}>{info.row.original.hostname}</button>)
|
||||||
},
|
},
|
||||||
{ header: "IP", accessorKey: "ip" },
|
{ header: "IP", accessorKey: "ip" },
|
||||||
{ header: "MAC", accessorKey: "mac", enableHiding: true },
|
{ header: "MAC", accessorKey: "mac", enableHiding: true },
|
||||||
@@ -310,7 +334,8 @@ const SimpleTable = () => {
|
|||||||
{ header: "Arquitectura", accessorKey: "architecture" },
|
{ header: "Arquitectura", accessorKey: "architecture" },
|
||||||
{
|
{
|
||||||
header: "Usuarios y Claves",
|
header: "Usuarios y Claves",
|
||||||
cell: ({ row }: CellContext<Equipo, any>) => {
|
cell: (info: CellContext<Equipo, any>) => {
|
||||||
|
const { row } = info;
|
||||||
const usuarios = row.original.usuarios || [];
|
const usuarios = row.original.usuarios || [];
|
||||||
return (
|
return (
|
||||||
<div className={styles.userList}>
|
<div className={styles.userList}>
|
||||||
@@ -347,12 +372,13 @@ const SimpleTable = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: "Sector", id: 'sector', accessorFn: (row: Equipo) => row.sector?.nombre || 'Asignar',
|
header: "Sector", id: 'sector', accessorFn: (row: Equipo) => row.sector?.nombre || 'Asignar',
|
||||||
cell: ({ row }: CellContext<Equipo, any>) => {
|
cell: (info: CellContext<Equipo, any>) => {
|
||||||
|
const { row } = info;
|
||||||
const sector = row.original.sector;
|
const sector = row.original.sector;
|
||||||
return (
|
return (
|
||||||
<div className={styles.sectorContainer}>
|
<div className={styles.sectorContainer}>
|
||||||
<span className={`${styles.sectorName} ${sector ? styles.sectorNameAssigned : styles.sectorNameUnassigned}`}>{sector?.nombre || 'Asignar'}</span>
|
<span className={`${styles.sectorName} ${sector ? styles.sectorNameAssigned : styles.sectorNameUnassigned}`}>{sector?.nombre || 'Asignar'}</span>
|
||||||
<button onClick={() => setModalData(row.original)} className={styles.tableButton} data-tooltip-id={`editSector-${row.id}`}>✏️<Tooltip id={`editSector-${row.id}`} className={styles.tooltip} place="top">Cambiar Sector</Tooltip></button>
|
<button onClick={() => setModalData(row.original)} className={styles.tableButton} data-tooltip-id={`editSector-${row.original.id}`}>✏️<Tooltip id={`editSector-${row.original.id}`} className={styles.tooltip} place="top">Cambiar Sector</Tooltip></button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user