Feat Modo Oscuro y Otras Estéticas

This commit is contained in:
2025-10-09 17:03:53 -03:00
parent 5f72f30931
commit d9da1c82c9
19 changed files with 702 additions and 257 deletions

View File

@@ -9,8 +9,10 @@ import { Tooltip } from 'react-tooltip';
import toast from 'react-hot-toast';
import type { Equipo, Sector, Usuario, UsuarioEquipoDetalle } from '../types/interfaces';
import styles from './SimpleTable.module.css';
import skeletonStyles from './Skeleton.module.css'; // Importamos el estilo del esqueleto
import { equipoService, sectorService, usuarioService } from '../services/apiService';
import { PlusCircle, KeyRound, UserX, Pencil, ArrowUp, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';
import ModalAnadirEquipo from './ModalAnadirEquipo';
import ModalEditarSector from './ModalEditarSector';
@@ -19,6 +21,7 @@ import ModalDetallesEquipo from './ModalDetallesEquipo';
import ModalAnadirDisco from './ModalAnadirDisco';
import ModalAnadirRam from './ModalAnadirRam';
import ModalAnadirUsuario from './ModalAnadirUsuario';
import TableSkeleton from './TableSkeleton'; // Importamos el componente de esqueleto
const SimpleTable = () => {
const [data, setData] = useState<Equipo[]>([]);
@@ -317,23 +320,23 @@ const SimpleTable = () => {
}
};
// --- DEFINICIÓN DE COLUMNAS CORREGIDA ---
const columns: ColumnDef<Equipo>[] = [
{ header: "ID", accessorKey: "id", enableHiding: true },
{
header: "Nombre", accessorKey: "hostname",
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", id: 'ip' },
{ header: "MAC", accessorKey: "mac", enableHiding: true },
{ header: "Motherboard", accessorKey: "motherboard" },
{ header: "CPU", accessorKey: "cpu" },
{ header: "RAM", accessorKey: "ram_installed" },
{ header: "RAM", accessorKey: "ram_installed", id: 'ram' },
{ header: "Discos", accessorFn: (row: Equipo) => row.discos?.map(d => `${d.mediatype} ${d.size}GB`).join(" | ") || "Sin discos" },
{ header: "OS", accessorKey: "os" },
{ header: "Arquitectura", accessorKey: "architecture" },
{ header: "Arquitectura", accessorKey: "architecture", id: 'arch' },
{
header: "Usuarios y Claves",
id: 'usuarios',
cell: (info: CellContext<Equipo, any>) => {
const { row } = info;
const usuarios = row.original.usuarios || [];
@@ -351,7 +354,7 @@ const SimpleTable = () => {
className={styles.tableButton}
data-tooltip-id={`edit-${u.id}`}
>
<KeyRound size={16} />
<Tooltip id={`edit-${u.id}`} className={styles.tooltip} place="top">Cambiar Clave</Tooltip>
</button>
@@ -360,7 +363,7 @@ const SimpleTable = () => {
className={styles.deleteUserButton}
data-tooltip-id={`remove-${u.id}`}
>
🗑
<UserX size={16} />
<Tooltip id={`remove-${u.id}`} className={styles.tooltip} place="top">Eliminar Usuario</Tooltip>
</button>
</div>
@@ -378,7 +381,7 @@ const SimpleTable = () => {
return (
<div className={styles.sectorContainer}>
<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.original.id}`}><Tooltip id={`editSector-${row.original.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}`}><Pencil size={16} /><Tooltip id={`editSector-${row.original.id}`} className={styles.tooltip} place="top">Cambiar Sector</Tooltip></button>
</div>
);
}
@@ -410,8 +413,18 @@ const SimpleTable = () => {
if (isLoading) {
return (
<div style={{ padding: '2rem', textAlign: 'center' }}>
<h2>Cargando Equipos...</h2>
<div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h2>Equipos (...)</h2>
<div className={`${skeletonStyles.skeleton}`} style={{ height: '40px', width: '160px' }}></div>
</div>
<div className={styles.controlsContainer}>
<div className={`${skeletonStyles.skeleton}`} style={{ height: '38px', width: '300px' }}></div>
<div className={`${skeletonStyles.skeleton}`} style={{ height: '38px', width: '200px' }}></div>
</div>
<div><p style={{ fontSize: '12px', fontWeight: 'bold' }}>** La tabla permite ordenar por múltiples columnas manteniendo shift al hacer click en la cabecera. **</p></div>
<div className={`${skeletonStyles.skeleton}`} style={{ height: '54px', marginBottom: '1rem' }}></div>
<TableSkeleton rows={15} columns={11} />
</div>
);
}
@@ -420,16 +433,16 @@ const SimpleTable = () => {
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '1rem 0' }}>
<div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
<button onClick={() => table.setPageIndex(0)} disabled={!table.getCanPreviousPage()} className={styles.tableButton}>
{'<<'}
<ChevronsLeft size={18} />
</button>
<button onClick={() => table.previousPage()} disabled={!table.getCanPreviousPage()} className={styles.tableButton}>
{'<'}
<ChevronLeft size={18} />
</button>
<button onClick={() => table.nextPage()} disabled={!table.getCanNextPage()} className={styles.tableButton}>
{'>'}
<ChevronRight size={18} />
</button>
<button onClick={() => table.setPageIndex(table.getPageCount() - 1)} disabled={!table.getCanNextPage()} className={styles.tableButton}>
{'>>'}
<ChevronsRight size={18} />
</button>
</div>
<span>
@@ -474,8 +487,9 @@ const SimpleTable = () => {
<button
className={`${styles.btn} ${styles.btnPrimary}`}
onClick={() => setIsAddModalOpen(true)}
style={{ display: 'flex', alignItems: 'center', gap: '8px' }}
>
+ Añadir Equipo
<PlusCircle size={18} /> Añadir Equipo
</button>
</div>
<div className={styles.controlsContainer}>
@@ -495,23 +509,41 @@ const SimpleTable = () => {
<thead>
{table.getHeaderGroups().map(hg => (
<tr key={hg.id}>
{hg.headers.map(h => (
<th key={h.id} className={styles.th} onClick={h.column.getToggleSortingHandler()}>
{flexRender(h.column.columnDef.header, h.getContext())}
{h.column.getIsSorted() && (<span className={styles.sortIndicator}>{h.column.getIsSorted() === 'asc' ? '⇑' : '⇓'}</span>)}
</th>
))}
{hg.headers.map(h => {
const classNames = [styles.th];
if (h.id === 'ip') classNames.push(styles.thIp);
if (h.id === 'ram') classNames.push(styles.thRam);
if (h.id === 'arch') classNames.push(styles.thArch);
if (h.id === 'usuarios') classNames.push(styles.thUsers);
if (h.id === 'sector') classNames.push(styles.thSector);
return (
<th key={h.id} className={classNames.join(' ')} onClick={h.column.getToggleSortingHandler()}>
{flexRender(h.column.columnDef.header, h.getContext())}
{h.column.getIsSorted() && (<span className={styles.sortIndicator}>{h.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>
))}
{row.getVisibleCells().map(cell => {
const classNames = [styles.td];
if (cell.column.id === 'ip') classNames.push(styles.tdIp);
if (cell.column.id === 'ram') classNames.push(styles.tdRam);
if (cell.column.id === 'arch') classNames.push(styles.tdArch);
if (cell.column.id === 'usuarios') classNames.push(styles.tdUsers);
if (cell.column.id === 'sector') classNames.push(styles.tdSector);
return (
<td key={cell.id} className={classNames.join(' ')}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
);
})}
</tr>
))}
</tbody>
@@ -520,7 +552,7 @@ const SimpleTable = () => {
{PaginacionControles}
{showScrollButton && (<button onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })} className={styles.scrollToTop} title="Volver arriba"></button>)}
{showScrollButton && (<button onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })} className={styles.scrollToTop} title="Volver arriba"><ArrowUp size={24} /></button>)}
{modalData && <ModalEditarSector modalData={modalData} setModalData={setModalData} sectores={sectores} onClose={() => setModalData(null)} onSave={handleSave} />}