import React, { useEffect, useState } from 'react';
export const serviceAreas = { clients: 'Clientes', services: 'Serviços', service_drafts: 'Prestações em rascunho', billing_drafts: 'Rascunhos de facturação' };
const actions = { view: 'Consultar', create: 'Criar', edit: 'Editar', manage: 'Gerir concessões' };
const isDraft = area => area === 'service_drafts' || area === 'billing_drafts';
const newLine = () => ({ service_id: '', quantity: '', unit_price: '', description: '' });
export default function ServicesWorkspace({ area, context, can, api, perform, busy, initialId, navigate }) {
const unit = isDraft(area) ? context.unit?.id ?? null : null;
const allowed = action => can(`${area}.${action}`, unit);
const [records, setRecords] = useState([]);
const [selected, setSelected] = useState(null);
const [form, setForm] = useState(null);
const [references, setReferences] = useState({ clients: [], services: [], source: [] });
const [error, setError] = useState('');
const [key, setKey] = useState(() => crypto.randomUUID());
const [access, setAccess] = useState(false);
const path = `/api/a/${area}`;
async function load() { const result = await api(path); setRecords(result.records); }
async function open(id) { setError(''); try { setSelected(await api(`${path}/${id}`)); setForm(null); } catch (e) { setError(e.message); } }
useEffect(() => { load().catch(e => setError(e.message)); if (initialId) open(initialId); }, [area, context.token]);
async function start(record = null) {
setError(''); setAccess(false);
try {
if (isDraft(area)) {
const [clients, services, source] = await Promise.all([
api('/api/a/clients'), api('/api/a/services'), area === 'billing_drafts' ? api('/api/a/service_drafts') : Promise.resolve({ records: [] })
]);
setReferences({ clients: clients.records, services: services.records, source: source.records });
}
setSelected(record);
setForm(record ? { name: record.name, description: record.data.description || '', client_id: record.client_id || '', source_id: record.source_id || '', currency: record.data.currency || '', lines: record.data.lines?.map(line => ({ ...line, quantity: line.quantity ?? '', unit_price: line.unit_price ?? '' })) || [newLine()] }
: { name: '', description: '', client_id: '', source_id: '', currency: '', lines: [newLine()] });
setKey(crypto.randomUUID());
} catch (e) { setError(e.message); }
}
async function save(event) {
event.preventDefault();
let body = { name: form.name.trim() };
if (area === 'services') body.description = form.description || null;
if (isDraft(area)) {
body = { ...(form.name.trim() ? { name: form.name.trim() } : {}), description: form.description || null };
if (area === 'billing_drafts' && !selected) body.source_id = Number(form.source_id);
else {
body.currency = form.currency || null;
body.lines = form.lines.map(line => ({ service_id: Number(line.service_id), description: line.description || null, quantity: line.quantity || null, unit_price: line.unit_price || null }));
if (area === 'service_drafts') body.client_id = Number(form.client_id);
}
}
if (selected) body.version = selected.version;
await perform(async () => {
const result = await api(selected ? `${path}/${selected.id}` : path, selected ? 'PATCH' : 'POST', body, context.token, key);
await load(); await open(result.id); setKey(crypto.randomUUID());
}, 'Rascunho ou cadastro guardado com histórico e captura durável.');
}
async function prepareBilling() {
await perform(async () => {
const result = await api('/api/a/billing_drafts', 'POST', { source_id: selected.id }, context.token, key);
navigate('billing_drafts', result.id);
}, 'Rascunho de facturação preparado a partir da prestação. Nenhuma factura foi emitida.');
}
function changeLine(index, field, value) { setForm({ ...form, lines: form.lines.map((line, n) => n === index ? { ...line, [field]: value } : line) }); }
if (!allowed('view')) return Sem consulta autorizada neste contexto.{serviceAreas[area]}
ENTREGA A · PREPARAÇÃO
{isDraft(area) ? `Unidade: ${context.unit?.name || 'não seleccionada'}` : 'Cadastro partilhado pelas unidades da empresa'}
Ainda não há registos neste âmbito.
}{records.map(record => )}Identificador interno #{selected.id} · Versão {selected.version}{isDraft(area) ? ' · Estado: rascunho' : ''}
{selected.data.description &&{selected.data.description}
} {isDraft(area) && <>Cliente: {selected.data.client_name}
{selected.source_id &&Origem: prestação #{selected.source_id}, versão {selected.data.source_version}
}Moeda: {selected.data.currency || 'Não indicada — pendente'}
| Serviço | Quantidade | Preço proposto |
|---|---|---|
| {line.service_name} | {line.quantity ?? 'Por preencher'} | {line.unit_price ?? 'Por preencher'} |
Base aritmética: {selected.base_amount ?? 'Incompleta'} {selected.data.currency || '(moeda pendente)'}
Esta base não é um total fiscal ou valor definitivo a pagar. Imposto por validar; contabilidade não processada.
{Object.keys(selected.pending).length > 0 &&{entry.created_at} · Autor #{entry.actor_id} · Evento #{entry.event?.id} · Capturado, sem processamento contabilístico
{JSON.stringify(entry.snapshot, null, 2)}Os dados e o histórico aparecem aqui. As operações disponíveis dependem das suas concessões explícitas.
>}Âmbito: {context.company.name}{isDraft(area) ? ` / ${context.unit?.name}` : ' — cadastro da empresa, partilhado entre unidades'}. Apenas pode delegar direitos que possui neste âmbito.
{error &&{error}
}{state && <>| Utilizador | Concessão |
|---|---|
| {grant.name}{grant.email} | {actions[grant.permission.split('.').at(-1)]} |