chore: import DH V2 D5.6.4 production baseline
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
import { useState } from 'react';
|
||||
import { NavLink, Outlet, useNavigate } from 'react-router';
|
||||
import { useAuth } from '../auth/AuthContext';
|
||||
import { APP_PHASE, APP_VERSION } from '../config/version';
|
||||
import { Icon } from '../components/Icon';
|
||||
import { OperationalContextBar } from '../components/OperationalContextBar';
|
||||
import type { IconName } from '../components/Icon';
|
||||
|
||||
interface NavItem {
|
||||
to: string;
|
||||
label: string;
|
||||
icon: IconName;
|
||||
permission?: string;
|
||||
}
|
||||
|
||||
const operational: NavItem[] = [
|
||||
{ to: '/', label: 'Inicio', icon: 'home', permission: 'dashboard.read' },
|
||||
{ to: '/inspecciones', label: 'Inspecciones', icon: 'clipboard', permission: 'inspections.read' },
|
||||
];
|
||||
|
||||
const followUp: NavItem[] = [
|
||||
{ to: '/hallazgos', label: 'Hallazgos', icon: 'alert', permission: 'inspection_findings.read' },
|
||||
];
|
||||
|
||||
const documents: NavItem[] = [
|
||||
{ to: '/actas', label: 'Actas', icon: 'clipboard', permission: 'inspection_acts.read' },
|
||||
{ to: '/informes', label: 'Informes', icon: 'audit', permission: 'inspection_reports.read' },
|
||||
];
|
||||
|
||||
const master: NavItem[] = [
|
||||
{ to: '/inventarios', label: 'Inventarios', icon: 'layers', permission: 'assets.read' },
|
||||
{ to: '/mapa', label: 'Mapa', icon: 'map', permission: 'assets.read' },
|
||||
{ to: '/importaciones', label: 'Importaciones', icon: 'upload', permission: 'asset_imports.read' },
|
||||
{ to: '/relevamiento', label: 'Relevamientos', icon: 'clipboard', permission: 'surveys.read' },
|
||||
];
|
||||
|
||||
const administration: NavItem[] = [
|
||||
{ to: '/admin/users', label: 'Usuarios', icon: 'users', permission: 'users.read' },
|
||||
{ to: '/admin/roles', label: 'Roles y permisos', icon: 'shield', permission: 'roles.read' },
|
||||
{ to: '/admin/asset-types', label: 'Configuración de Inventarios', icon: 'layers', permission: 'asset_types.read' },
|
||||
{ to: '/admin/finding-catalog', label: 'Catálogo de hallazgos', icon: 'alert', permission: 'finding_catalog.manage' },
|
||||
{ to: '/admin/document-delivery', label: 'Entrega documental', icon: 'audit', permission: 'document_delivery.read' },
|
||||
];
|
||||
|
||||
const system: NavItem[] = [
|
||||
{ to: '/admin/audit', label: 'Auditoría', icon: 'audit', permission: 'audit.read' },
|
||||
];
|
||||
|
||||
export function AppLayout() {
|
||||
const { user, hasPermission, logout } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const [mobileOpen, setMobileOpen] = useState(false);
|
||||
const canSee = (item: NavItem) => !item.permission || hasPermission(item.permission);
|
||||
const close = () => setMobileOpen(false);
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logout().catch(() => undefined);
|
||||
navigate('/login', { replace: true });
|
||||
};
|
||||
|
||||
const renderItems = (items: NavItem[]) => items.filter(canSee).map((item) => (
|
||||
<NavLink
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
end={item.to === '/'}
|
||||
onClick={close}
|
||||
className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}
|
||||
>
|
||||
<Icon name={item.icon} />
|
||||
<span>{item.label}</span>
|
||||
</NavLink>
|
||||
));
|
||||
|
||||
const hasFollowUp = followUp.some(canSee);
|
||||
const hasDocuments = documents.some(canSee);
|
||||
const hasMaster = master.some(canSee);
|
||||
const hasAdmin = administration.some(canSee);
|
||||
const hasSystem = system.some(canSee);
|
||||
|
||||
return (
|
||||
<div className="shell">
|
||||
{mobileOpen && <button className="sidebar-backdrop" aria-label="Cerrar menú" onClick={close} />}
|
||||
<aside className={`sidebar ${mobileOpen ? 'open' : ''}`}>
|
||||
<div className="brand">
|
||||
<span className="brand-mark">DH</span>
|
||||
<div><strong>DH Inspección</strong><small>Plataforma V2</small></div>
|
||||
</div>
|
||||
|
||||
<nav className="sidebar-nav" aria-label="Navegación principal">
|
||||
<span className="nav-heading">OPERACIÓN</span>
|
||||
{renderItems(operational)}
|
||||
{hasFollowUp && <span className="nav-heading spaced">SEGUIMIENTO</span>}
|
||||
{renderItems(followUp)}
|
||||
{hasDocuments && <span className="nav-heading spaced">DOCUMENTOS</span>}
|
||||
{renderItems(documents)}
|
||||
{hasMaster && <span className="nav-heading spaced">INVENTARIOS</span>}
|
||||
{renderItems(master)}
|
||||
{hasAdmin && <span className="nav-heading spaced">ADMINISTRACIÓN</span>}
|
||||
{renderItems(administration)}
|
||||
{hasSystem && <span className="nav-heading spaced">SISTEMA</span>}
|
||||
{renderItems(system)}
|
||||
</nav>
|
||||
|
||||
<div className="sidebar-user">
|
||||
<div className="user-avatar">{user?.firstName?.[0]}{user?.lastName?.[0]}</div>
|
||||
<div className="user-copy">
|
||||
<strong>{user?.firstName} {user?.lastName}</strong>
|
||||
<small>{user?.roles.join(' · ') || user?.username}</small>
|
||||
</div>
|
||||
<button className="icon-button dark" onClick={handleLogout} title="Cerrar sesión" aria-label="Cerrar sesión">
|
||||
<Icon name="logout" />
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div className="main-column">
|
||||
<header className="mobile-header">
|
||||
<button className="icon-button" onClick={() => setMobileOpen(true)} aria-label="Abrir menú"><Icon name="menu" /></button>
|
||||
<strong>DH Inspección</strong>
|
||||
<span className="mobile-version">v{APP_VERSION}</span>
|
||||
</header>
|
||||
<OperationalContextBar />
|
||||
<main className="content"><Outlet /></main>
|
||||
<footer className="app-footer" aria-label="Versión de la aplicación">
|
||||
<span>DH Inspección V2</span><span>·</span><strong>v{APP_VERSION}</strong><span>·</span><span>{APP_PHASE}</span>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user