SGF TEMPO
{error || 'A carregar…'}
import React, { useEffect, useState } from 'react';
import { createRoot } from 'react-dom/client';
import '../css/app.css';
import ServicesWorkspace, { serviceAreas } from './ServicesWorkspace.jsx';
import DocumentPreparationWorkspace from './DocumentPreparationWorkspace.jsx';
import { DocumentPreviewConfiguration } from './DocumentPreview.jsx';
import CatalogueAccountingWorkspace from './CatalogueAccountingWorkspace.jsx';
import ServiceFiscalWorkspace from './ServiceFiscalWorkspace.jsx';
import ServiceInvoiceWorkspace from './ServiceInvoiceWorkspace.jsx';
import ServiceReceivablesWorkspace from './ServiceReceivablesWorkspace.jsx';
import WorkshopWorkspace from './WorkshopWorkspace.jsx';
import DirectCostsWorkspace from './DirectCostsWorkspace.jsx';
import IndustrialCostsWorkspace from './IndustrialCostsWorkspace.jsx';
const labels = { 'catalogue_accounting.view':'Consultar classificação contabilística', 'catalogue_accounting.propose':'Preparar classificação contabilística', 'catalogue_accounting.approve':'Validar ou recusar classificação contabilística', 'company.view': 'Consultar empresa', 'company.edit': 'Editar nome da empresa', 'unit.create': 'Criar unidades', 'access.manage': 'Gerir acessos', 'unit.view': 'Consultar unidade', 'unit.edit': 'Editar nome da unidade' };
function App() {
const [state, setState] = useState(null);
const [error, setError] = useState('');
const [notice, setNotice] = useState('');
const [busy, setBusy] = useState(false);
const [page, setPage] = useState('home');
const [aSelection, setASelection] = useState(null);
const context = state?.context;
const can = (permission, unit = null) => !!context?.grants.some(g => g.permission === permission && g.unit_id === unit);
async function api(path, method = 'GET', body, token = context?.token, commandKey = null) {
const response = await fetch(path, { method, credentials: 'same-origin', headers: { Accept: 'application/json', 'Content-Type': 'application/json', 'X-CSRF-TOKEN': state?.csrf || '', 'X-SGF-Context': token || '', ...(commandKey ? { 'Idempotency-Key': commandKey } : {}) }, ...(body === undefined ? {} : { body: JSON.stringify(body) }) });
const result = await response.json().catch(() => ({}));
if (!response.ok) {
if (response.status === 401) { await refresh(); }
const message = response.status === 419 ? 'A sessão expirou. Actualize a página e entre novamente.' : Object.values(result.errors || {}).flat().join(' ') || result.message || 'Não foi possível concluir a operação.';
throw new Error(message);
}
return result;
}
async function refresh() {
const response = await fetch('/api/session', { credentials: 'same-origin', headers: { Accept: 'application/json' } });
if (!response.ok) throw new Error('Não foi possível carregar a sessão.');
const next = await response.json(); setState(next);
if (next.notice) setNotice(next.notice);
}
useEffect(() => { refresh().catch(e => setError(e.message)); }, []);
async function perform(operation, message) {
setBusy(true); setError(''); setNotice('');
try { await operation(); await refresh(); if (message) setNotice(message); }
catch (e) { setError(e.message); }
finally { setBusy(false); }
}
async function select(companyId, unitId = null) {
const company = state.companies.find(c => c.id === Number(companyId));
if (!company) return;
await perform(() => api('/api/context', 'POST', { tenant_id: company.tenant_id, company_id: company.id, unit_id: unitId }), 'Contexto alterado. Os formulários foram recarregados.');
}
if (!state) return {error || 'A carregar…'} SGF TEMPO · PREPARAÇÃO LOCAL A aplicação está pronta, mas ainda não existe um utilizador. O login ficará disponível depois de definires a palavra-passe no procedimento local já autorizado. Executa este comando, confere o âmbito apresentado e define a palavra-passe nos dois pedidos de entrada oculta: Não envies a palavra-passe pelo chat. Nenhuma conta é criada por este ecrã. {error}SGF TEMPO
Falta concluir a primeira conta
Concluir no terminal do VS Code
powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\laragon\data\sgf-tempo\run\complete-first-admin.ps1
BEM-VINDO AO SGF TEMPO
Use a conta preparada pelo procedimento local autorizado.
{error &&Precisa de recuperar o acesso? Solicite recuperação local assistida. Não existe recuperação por email nesta etapa.
Apenas os contextos com acesso explícito são apresentados.
A conta existe, mas ainda não tem acesso a uma empresa. Peça ao responsável uma concessão explícita.
Seleccione uma empresa. A pertença a um cliente contratante não dá acesso automático às suas empresas ou unidades.
ÁREA INICIAL
O contexto permanece visível e cada operação é validada pelo servidor.
Cliente contratante #{context.company.tenant_id} · Empresa #{context.company.id}
{context.units.length} unidade(s) com consulta autorizada
DADOS BÁSICOS
O seu acesso permite consulta. A edição exige uma concessão própria.
}DADOS BÁSICOS
Não tem consulta concedida a nenhuma unidade desta empresa.
}A unidade será criada nesta empresa. Receberá consulta e edição explícitas apenas para a nova unidade.
A gestão de acessos exige uma concessão própria.
AUTORIZAÇÕES EXPLÍCITAS
Actua apenas nesta empresa e pode delegar apenas direitos que possui no mesmo âmbito. Os utilizadores são preparados pelo procedimento local.
{loadError &&{loadError}
}| Utilizador | Permissão | Âmbito |
|---|---|---|
| {member?.name}{member?.email} | {labels[g.permission]} | {g.unit_id ? context.units.find(u => u.id === g.unit_id)?.name || 'Unidade autorizada' : 'Empresa'} |
A carregar acessos…
}