import React, { useEffect, useRef, useState } from 'react';
import {visibleWorkshopSections} from './workshop-menu.js';
const kindLabels={assignment:'Responsável atribuído',task:'Tarefa',authorization:'Autorização registada',start:'Início autorizado',time:'Tempo relatado',material:'Material observado'};
const eventLabels={required:'Requisitado',delivered:'Entregue',consumed:'Aplicado/consumido',returned:'Devolvido'};
const decisionLabels={verified:'Verificado',refused:'Recusado',acknowledged:'Ocorrência reconhecida para auditoria — continua fora do âmbito'};
const iso=v=>new Date(v).toISOString();
export default function WorkshopWorkspace({context,can,api,busy,initialOrderId=null,navigate}) {
const [data,setData]=useState(null),[orderId,setOrderId]=useState(initialOrderId??''),[error,setError]=useState(''),[working,setWorking]=useState(false),[taskId,setTaskId]=useState('');
const attempt=useRef(null);
const allowed=r=>can(`workshop.${r}`,context.unit?.id??null);
const load=async()=>{const d=await api('/api/workshop');setData(d);return d;};
useEffect(()=>{load().catch(e=>setError(e.message));},[context.token]);
const order=data?.orders.find(o=>String(o.id)===String(orderId))??data?.orders[0];
const tasks=order?.entries.filter(e=>e.kind==='task'&&e.current)??[];
const task=tasks.find(e=>String(e.id)===String(taskId))??tasks[0];
const authorizations=order?.entries.filter(e=>e.kind==='authorization'&&e.current&&e.task_id===task?.id)??[];
const auth=authorizations.at(-1);
const starts=order?.entries.filter(e=>e.kind==='start'&&!order.entries.some(t=>t.start_id===e.id))??[];
const disabled=busy||working;
async function send(path,input) {
setWorking(true);setError(''); const canonical=JSON.stringify([path,input]);
if(attempt.current?.canonical!==canonical)attempt.current={canonical,key:crypto.randomUUID()};
try{await api('/api/workshop'+path,'POST',input,context.token,attempt.current.key);await load();attempt.current=null;}
catch(e){setError(e.message);}finally{setWorking(false);}
}
function entry(kind,values) {
const d={...values};
if(d.previous_id) {
const previous=order.entries.find(e=>e.id===Number(d.previous_id));
if(!previous){setError('Seleccione uma origem existente desta ordem.');return;}
d.previous_id=previous.id; d.origin=previous.origin; d.version=previous.version+1;
} else {delete d.previous_id;d.version=1;}
return send(`/orders/${order.id}/entries`,{kind,revision:order.revision,...d});
}
const correctionFields=kind=><>Versão anterior a corrigir (opcional)Novo acontecimento {order.entries.filter(e=>e.kind===kind&&e.current).map(e=>#{e.id} · {e.origin} v{e.version} )} Motivo de correcção (se aplicável) >;
return <>
{navigate&&{visibleWorkshopSections(can,context.unit?.id??null).map(s=>{if(s.id!=='workshop')navigate(s.id);}}>{s.label} )} }
WORKSHOP-RESOURCE-RECORD-v1
Oficina — registo de recursos TESTE — SEM UTILIZAÇÃO OPERACIONAL Sem stock, custos, facturação ou recebimentos.
{error&&{error}
}load().catch(e=>setError(e.message))}>Actualizar revisão explicitamente Abertura não autoriza trabalho. Relatar uma ocorrência passada não lhe dá autorização retroactiva. Alterações concorrentes exigem nova revisão.
{!data?
A carregar…
:data.blocked?Utilização operacional bloqueada Disponível apenas na instância isolada ligada à base de testes.
:<>
{allowed('open')&&Abrir ordem de teste Para reutilizar equipamento já identificado, a API aceita o seu ID; não criar outro só por existir uma nova ordem.
}
Ordens autorizadas para consulta Ordem{setOrderId(e.target.value);setTaskId('');}}>{data.orders.map(o=>{o.reference} · #{o.id} )} {order?<>Cliente #{order.client_id} · {order.equipment.identifier} · {order.equipment.description} · {order.location}
{order.request}
Revisão {order.revision} · Responsável #{order.assigned_to??'por atribuir'} · {({open:'Aberta',in_review:'Apontamentos em revisão',exception_review:'Ocorrências fora do âmbito para revisão',records_verified:'Registos verificados — sem conclusão comercial'})[order.state]}
Tempo efectivo relatado
{order.minutes_reported} MIN Tempo efectivo verificado
{order.minutes_verified} MIN >:Sem ordens visíveis neste âmbito/atribuição.
}
{order&&<>
{allowed('assign')&&}
Tarefa e âmbito Versão da tarefasetTaskId(e.target.value)}>{tasks.map(t=>#{t.id} · {t.data.description} · v{t.version} )} {auth?`Autorização #${auth.id}: ${auth.data.authorized_by} · ${auth.data.authorized_at} · ${auth.data.scope}`:'Sem autorização demonstrada para esta versão.'}
{allowed('record')&&}
{allowed('authorize')&&task&&}
{allowed('record')&&task&&<>
Relatar tempo efectivo {if(Boolean(v.pause_start)!==Boolean(v.pause_end)){setError('Indique início e fim da pausa; não será ignorada.');return;} const d={origin:v.origin,task_id:task.id,...(auth?{authorization_id:auth.id}:{}),mode:v.start_id?'started':'reported',time_type:v.time_type,starts_at:iso(v.starts_at),ends_at:iso(v.ends_at),evidence:v.evidence,previous_id:v.previous_id,correction_reason:v.correction_reason,pauses:[]}; if(v.pause_start&&v.pause_end)d.pauses=[{start:iso(v.pause_start),end:iso(v.pause_end)}]; if(v.start_id)d.start_id=Number(v.start_id);return entry('time',d);}}>Identificador da ocorrência de tempo Início registado (opcional)Relato de ocorrência já acontecida {starts.map(s=>#{s.id} · {s.starts_at} · tarefa #{s.task_id} )} Tipo de tempoTrabalho Deslocação Início efectivo Fim efectivo Início da pausa (opcional) Fim da pausa (opcional) Evidência da ocorrência {correctionFields('time')} A API suporta várias pausas; este formulário inicial recebe uma. Intervalos incompletos ou não representáveis exactamente em minutos são recusados.
Registar material observado entry('material',{...v,task_id:task.id,...(auth?{authorization_id:auth.id}:{}),starts_at:iso(v.starts_at)})}>Identificador da ocorrência material Referência estável do material Ocorrência{Object.entries(eventLabels).map(([k,v])=>{v} )} PropriedadeEmpresa Cliente Unidade de medida{['UN','KG','M','L'].map(u=>{u} )} Quantidade observada Data/hora da ocorrência Evidência de origem {correctionFields('material')} Sem reserva, armazém, custo ou saída de stock. Divergência física fica visível para revisão.
>}
Materiais por referência, propriedade e unidade {order.materials.map(m=>{m.resource} · {m.unit} · {m.owner==='client'?'Cliente':'Empresa'} Requisitado {m.required} · Entregue {m.delivered} · Aplicado {m.consumed} · Devolvido {m.returned} · Aplicação verificada {m.verified_consumed}
Diferença física a explicar: {m.unexplained} {m.unit}. São observações, não saldo de armazém.
)}
Apontamentos, versões e verificação {order.entries.map(e=>#{e.id} · {kindLabels[e.kind]} · {e.origin} v{e.version}{!e.current?' · Histórico substituído':''} {e.data.description||e.data.scope||e.data.evidence} · Autor #{e.actor_id}
{e.minutes!=null&&{e.starts_at} a {e.ends_at} · {e.minutes} MIN efectivos · {e.data.time_type==='travel'?'Deslocação':'Trabalho'}
}{e.quantity!=null&&{eventLabels[e.data.event]} · {e.quantity} {e.data.unit} · {e.data.resource}
}{e.outside_authority&&FORA DO ÂMBITO AUTORIZADO — relato para auditoria. Não autoriza execução nem ganha cobertura retroactiva.
}{e.decision?`${decisionLabels[e.decision.decision]} · ${e.decision.reason} · Verificador #${e.decision.actor_id}`:e.current&&['task','time','material'].includes(e.kind)?'Aguarda verificação explícita.':''}
{allowed('verify')&&e.current&&!e.decision&&['task','time','material'].includes(e.kind)&&send(`/entries/${e.id}/verification`,{version:e.version,revision:order.revision,decision:e.outside_authority?'acknowledged':'verified',reason:v.reason})}>Fundamento da revisão {const form=event.currentTarget.form;if(!form.reportValidity())return;send(`/entries/${e.id}/verification`,{version:e.version,revision:order.revision,decision:'refused',reason:String(new FormData(form).get('reason'))});}}>Recusar apontamento } )}
>}
>}
>;
}
function Form({children,label,disabled,save}) {return {e.preventDefault();save(Object.fromEntries(new FormData(e.currentTarget)));}}>{children}{label} ;}