import React, {useEffect, useRef, useState} from 'react'; import {visibleBillingSections} from './billing-menu.js'; import {visibleWorkshopSections} from './workshop-menu.js'; import {visibleStockSections} from './stock-menu.js'; import {visibleAccountingSections} from './accounting-menu.js'; export function navigationGroups(can, unit, capabilities={}) { 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:[{id:'purchasing_home',label:'Área de Compras',visible:can('company.view')},item('purchasing_budget','Controlo orçamental sintético','purchasing_budget.view',unit),item('supplier_payables','Obrigações a fornecedores — ensaio','supplier_payables.view',unit),item('supplier_payments','Pagamentos e afectações — ensaio','supplier_payables.view',unit)]}, {id:'stock',label:'Stock',status:'Em preparação',items:[...visibleStockSections(can).map(s=>({id:s.id,label:s.label,informational:true,visible:true})),...(capabilities.stockCatalogue?[item('stock_catalogue','Artigos e revisão do catálogo','stock.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:[...(capabilities.multilineInvoices?[item('multiline_invoices','Documento com várias linhas QA','billing_drafts.view',unit)]:[]),...visibleBillingSections(can,unit).map(s=>({id:s.id,label:s.label,visible:true}))]}, {id:'billing_support',label:'Configuração documental',items:[{id:'documents',label:'Preparação documental',visible:can('issuer_documents.view')||can('client_documents.view')||can('draft_documents.view',unit)},item('service_invoice_setup','Séries e emissão de teste','service_invoices.view',unit),item('preview_model','Apresentação do rascunho','issuer_documents.view')]}, {id:'treasury',label:'Fornecedores',items:[...(capabilities.payablePositions?[item('payable_positions','Posições de fornecedores','supplier_payables.view',unit),item('payable_aging','Mapa de antiguidade — fornecedores','supplier_payables.view',unit)]:[]),item('native_payables','Obrigações, pagamentos e afectações','supplier_payables.view',unit)]}, {id:'receivables',label:'Recebimentos e Caixa',items:[item('receivable_positions','Posições de clientes e vencimentos','service_receivables.view',unit),item('service_receivables','Recebimentos, afectações e reconciliação de teste','service_receivables.view',unit),item('financial_indicators','Critérios e indicadores QA','financial_analysis.view',unit),item('bank_reconciliation','Conferência de extracto QA','bank_reconciliation.view',unit),item('cash','Caixa — abertura, despesas e fecho QA','cash.view',unit)]}, {id:'workshop',label:'Oficinas',items:[...visibleWorkshopSections(can,unit).map(s=>({id:s.id,label:s.label,visible:true})),item('workshop_billing','Oficina → rascunho','workshop_billing.view',unit),item('workshop_corrections','Correcção e cancelamento comercial','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:[...(capabilities.fixedAssets?[item('fixed_assets','Activos e depreciações internas QA','fixed_assets.view',unit)]:[]),...visibleAccountingSections(can).map(s=>({id:s.id,label:s.label,informational:true,visible:true})),...(capabilities.internalStatements?[item('internal_statements','Demonstrações internas parciais QA','general_ledger.view',unit)]:[]),item('catalogue_accounting','Classificação contabilística','catalogue_accounting.view'),item('service_fiscal','Perfis fiscais do serviço','service_fiscal.view'),item('fiscal_iva','Livro e mapa interno de IVA QA','fiscal_assessments.view',unit),item('fiscal_rules','Regras fiscais versionadas QA','fiscal_rules.view',unit),...(capabilities.fiscalObligations?[item('fiscal_obligations','Obrigações e prazos administrativos QA','fiscal_obligations.view',unit)]:[])]}, ...(capabilities.remunerations?[{id:'human_resources',label:'Recursos Humanos',items:[item('remunerations','Cadastro e remunerações internas QA','human_resources.remunerations.view',unit),...(capabilities.personnel?[{id:'hr_personnel',label:'Contratos, assiduidade e férias',visible:['contracts','attendance','leave'].every(a=>can('human_resources.'+a+'.view',unit))}]:[])]}]:[]), {id:'admin',label:'Administração',items:[item('company','Empresa','company.view'),{id:'units',label:'Unidades autorizadas',visible:can('company.view')},item('module_records','Associações de módulos','access.manage'),item('access','Acessos','access.manage')]}, ].map(g=>({...g,items:g.items.filter(i=>i.visible)})).filter(g=>g.items.length); } export function navigationTopics(groups) { const layout=[ {id:'start',label:'Início',groups:['start']}, {id:'commercial',label:'Comercial',groups:['services','billing']}, {id:'purchasing',label:'Compras',groups:['purchasing']}, {id:'stock',label:'Stock',groups:['stock']}, {id:'workshop',label:'Oficinas',groups:['workshop']}, {id:'finance',label:'Financeiro',groups:['receivables','costs']}, {id:'accounting',label:'Contabilidade',groups:['accounting']}, {id:'settings',label:'Configuração',groups:['billing_support','admin']}, ]; const known=new Set(layout.flatMap(t=>t.groups)); return [...layout.map(t=>({...t,sections:t.groups.map(id=>groups.find(g=>g.id===id)).filter(Boolean)})), ...groups.filter(g=>!known.has(g.id)).map(g=>({id:g.id,label:g.label,sections:[g]}))] .filter(t=>t.sections.some(g=>g.items.length)); } export default function WorkspaceNavigation({groups,page,navigate,user,busy,logout}) { const [open,setOpen]=useState(null);const root=useRef(null);const triggers=useRef({}); const topics=navigationTopics(groups); useEffect(()=>{ const outside=e=>{if(root.current&&!root.current.contains(e.target))setOpen(null);}; const escape=e=>{if(e.key==='Escape'&&open){e.preventDefault();setOpen(null);triggers.current[open]?.focus();}}; document.addEventListener('pointerdown',outside);document.addEventListener('keydown',escape); return ()=>{document.removeEventListener('pointerdown',outside);document.removeEventListener('keydown',escape);}; },[open]); useEffect(()=>{setOpen(null);},[page,JSON.stringify(groups)]); function choose(id){navigate(id);setOpen(null);} function focusItem(e,topic){ if(!['ArrowDown','ArrowUp','Home','End'].includes(e.key))return; const items=[...root.current.querySelectorAll(`#topic-${topic} .topic-item`)]; if(!items.length)return;e.preventDefault();const index=items.indexOf(e.target); const next=e.key==='Home'?0:e.key==='End'?items.length-1:e.key==='ArrowDown'?(index+1)%items.length:(index-1+items.length)%items.length; items[next].focus(); } function openWithKeyboard(e,id){ if(e.key==='ArrowDown'||e.key==='ArrowUp'){ e.preventDefault();setOpen(id); requestAnimationFrame(()=>{const items=root.current?.querySelectorAll(`#topic-${id} .topic-item`);(e.key==='ArrowUp'?items?.[items.length-1]:items?.[0])?.focus();}); } } return
{if(e.relatedTarget&&!e.currentTarget.contains(e.relatedTarget))setOpen(null);}}> TEMPO SGF
{user.name}{user.email}
; }