Files
SGF/resources/js/WorkspaceNavigation.jsx
T
2026-10-29 18:43:29 +02:00

27 lines
4.1 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, {useRef, useState} from 'react';
export function navigationGroups(can, unit) {
const item=(id,label,permission,scope=null)=>({id,label,visible:can(permission,scope)});
return [
{id:'start',label:'Início',items:[{id:'home',label:'Página inicial',visible:true}]},
{id:'purchasing',label:'Compras',items:[item('purchasing_budget','Controlo orçamental sintético','purchasing_budget.view',unit),item('supplier_payables','Obrigações a fornecedores — ensaio','supplier_payables.view',unit)]},
{id:'services',label:'Clientes e serviços',items:[item('clients','Clientes','clients.view'),item('services','Catálogo de serviços','services.view'),item('service_drafts','Prestações em rascunho','service_drafts.view',unit)]},
{id:'billing',label:'Facturação',items:[{id:'documents',label:'Preparação documental',visible:can('issuer_documents.view')||can('client_documents.view')||can('draft_documents.view',unit)},item('billing_drafts','Rascunhos de facturação','billing_drafts.view',unit),item('service_invoices','Documentos e séries de teste','service_invoices.view',unit),item('preview_model','Apresentação do rascunho','issuer_documents.view')]},
{id:'receivables',label:'Recebimentos',items:[item('service_receivables','Recebimentos, afectações e reconciliação de teste','service_receivables.view',unit)]},
{id:'workshop',label:'Oficinas',items:[item('workshop','Ordens, tarefas e apontamentos de teste','workshop.view',unit),item('workshop_billing','Oficina → rascunho','workshop_billing.view',unit),item('workshop_corrections','Correcção e cancelamento','workshop_corrections.view',unit)]},
{id:'costs',label:'Custos e orçamento',items:[item('direct_costs','Custo directo de teste','direct_costs.view',unit),item('industrial_costs','Lotes e custos industriais de teste','industrial_costs.view',unit),item('industrial_budgets','Orçamentos e comparação','industrial_budgets.view',unit)]},
{id:'accounting',label:'Contabilidade e fiscalidade',items:[item('catalogue_accounting','Classificação contabilística','catalogue_accounting.view'),item('service_fiscal','Perfis fiscais do serviço','service_fiscal.view')]},
{id:'admin',label:'Administração',items:[item('company','Empresa','company.view'),{id:'units',label:'Unidades autorizadas',visible:can('company.view')},item('access','Acessos','access.manage')]},
].map(g=>({...g,items:g.items.filter(i=>i.visible)})).filter(g=>g.items.length);
}
export default function WorkspaceNavigation({groups,page,navigate,user,busy,logout}) {
const [expanded,setExpanded]=useState({});const drawer=useRef(null);
function choose(id){navigate(id);drawer.current?.close();}
function contents(prefix){return <><a className="brand" href="/">TEMPO<span>SGF</span></a><p className="sidebar-label">ESPAÇO DE TRABALHO</p><nav aria-label="Navegação principal">{groups.map(g=>{
const active=g.items.some(i=>i.id===page),open=active||!!expanded[g.id];
return <section className="navigation-group" key={g.id}><button className="navigation-heading" aria-expanded={open} aria-controls={`${prefix}-${g.id}`} onClick={()=>setExpanded(s=>({...s,[g.id]:!open}))}><span>{g.label}</span><span aria-hidden="true">{open?'−':'+'}</span></button><div id={`${prefix}-${g.id}`} hidden={!open}>{g.items.map(i=><button key={i.id} className={`navigation-item ${page===i.id?'active':''}`} aria-current={page===i.id?'page':undefined} onClick={()=>choose(i.id)}>{i.label}</button>)}</div></section>;
})}</nav><div className="sidebar-bottom"><strong>{user.name}</strong><small>{user.email}</small><button disabled={busy} onClick={logout}>Sair da aplicação</button></div></>;}
return <><button className="mobile-menu-toggle" onClick={()=>drawer.current.showModal()} aria-haspopup="dialog">Menu</button><aside className="sidebar grouped-sidebar">{contents('desktop-nav')}</aside><dialog className="navigation-drawer" ref={drawer} aria-label="Menu de navegação"><button className="drawer-close" onClick={()=>drawer.current.close()} autoFocus>Fechar menu</button><div className="sidebar">{contents('mobile-nav')}</div></dialog></>;
}