Files
SGF/resources/js/WorkshopOrderList.jsx
2026-11-11 09:00:17 +02:00

13 lines
5.9 KiB
React

import React,{useEffect,useState} from 'react';
import {workshopStates} from './workshop-menu.js';
import {checkedWorkshopReport,workshopPageCsv} from './workshop-report-view.js';
export function WorkshopOrderRow({row:r,navigate}){return <tr><td>{r.reference}<small>{r.client??'Cliente por conferir'}</small></td><td>{r.equipment??'Identificação por conferir'}<small>{r.description}</small><small>{r.serial??'Série não registada'}</small></td><td>{r.technician??'Por atribuir'}</td><td>{workshopStates[r.state]}</td><td>{r.points} registo(s)<small>{r.points_not_verified} sem verificação favorável</small></td><td>{r.minutes_reported} apontados<small>{r.minutes_verified} verificados</small></td><td><button onClick={()=>navigate('workshop',r.id)}>Abrir ordem e evidências</button></td></tr>;}
export default function WorkshopOrderList({context,api,navigate,report=false,equipmentId=null,onBack=null}){
const [form,setForm]=useState({search:'',state:'',per_page:'25',from:'',to:'',equipment_id:equipmentId??''}),[query,setQuery]=useState({page:1,per_page:'25',equipment_id:equipmentId??''}),[data,setData]=useState(null),[error,setError]=useState(''),[loading,setLoading]=useState(false);
useEffect(()=>{let current=true;setData(null);setError('');if(report&&!query.from){setLoading(false);return()=>{current=false;};}setLoading(true);api('/api/workshop/orders?'+new URLSearchParams(Object.entries(query).filter(([,v])=>v!==''))).then(v=>{if(current)setData(checkedWorkshopReport(v,query));}).catch(e=>{if(current)setError(e.message);}).finally(()=>{if(current)setLoading(false);});return()=>{current=false;};},[context.token,query,report]);
const p=data?.orders;const exportPage=()=>{const url=URL.createObjectURL(new Blob(['\uFEFF'+workshopPageCsv(data,query)],{type:'text/csv;charset=utf-8'}));const a=document.createElement('a');a.href=url;a.download='sgf-oficina-pagina-'+p.current_page+'.csv';a.click();setTimeout(()=>URL.revokeObjectURL(url),1000);};
return <section className="panel"><h2>{equipmentId?'Ordens do equipamento escolhido':report?'Relatório de ordens por período de abertura':'Ordens, responsáveis e apontamentos'}</h2>{report&&<p>Escolha as datas de abertura das ordens. Os apontamentos e a identificação são os actuais; não é um fecho diário ou uma reconstituição histórica.</p>}{onBack&&<button disabled={loading} onClick={onBack}>Voltar ao cadastro de equipamentos</button>}{data?.equipment&&<section><h3>Equipamento seleccionado</h3><dl className="overview-definitions"><div><dt>Código</dt><dd>{data.equipment.identifier}</dd></div><div><dt>Descrição</dt><dd>{data.equipment.description}</dd></div><div><dt>Série</dt><dd>{data.equipment.serial??'Não registada'}</dd></div><div><dt>Cliente</dt><dd>{data.equipment.client_name}</dd></div></dl><p>A identificação é a actual; os apontamentos não representam uma reparação concluída.</p></section>}<p>Consulta actual por empresa/unidade e atribuição ao técnico. O estado resume os apontamentos actuais, não a entrega, a factura ou o pagamento.</p><form data-sgf-read-filters="true" onSubmit={e=>{e.preventDefault();setQuery({...form,page:1});}}><div className="form-grid"><label>Abertas desde<input type="date" min="2000-01-01" max="2100-12-31" required={report||Boolean(form.to)} value={form.from} onChange={e=>setForm({...form,from:e.target.value})}/></label><label>Abertas até<input type="date" min={form.from||'2000-01-01'} max="2100-12-31" required={report||Boolean(form.from)} value={form.to} onChange={e=>setForm({...form,to:e.target.value})}/></label><label>Referência, cliente, equipamento, série ou responsável<input type="search" maxLength={100} value={form.search} onChange={e=>setForm({...form,search:e.target.value})}/></label><label>Estado dos apontamentos<select value={form.state} onChange={e=>setForm({...form,state:e.target.value})}><option value="">Todos</option>{Object.entries(workshopStates).map(([v,t])=><option key={v} value={v}>{t}</option>)}</select></label><label>Registos por página<select value={form.per_page} onChange={e=>setForm({...form,per_page:e.target.value})}><option value="25">25</option><option value="100">100</option></select></label></div><button disabled={loading}>Pesquisar</button><button type="button" disabled={loading} onClick={()=>setQuery(q=>({...q,page:1}))}>Actualizar consulta</button></form>
{loading&&<p role="status">A consultar a página autorizada…</p>}{error&&<p role="alert">{error}. Não é possível concluir que existem zero ordens ou pendências.</p>}{p&&<><p>{data.opening_period?`Abertura entre ${data.opening_period.from} e ${data.opening_period.to}, ${data.opening_period.zone}; fim exclusivo UTC ${data.opening_period.end_utc_exclusive}.`:'Sem filtro de abertura.'}</p><p role="status">{p.total} ordem(ns) para os filtros · página {p.current_page} de {p.last_page} · leitura {data.observed_at}.</p><div className="table-scroll"><table><thead><tr><th>Ordem e cliente</th><th>Equipamento</th><th>Responsável</th><th>Estado</th><th>Apontamentos actuais</th><th>Tempo em minutos</th><th>Consultar</th></tr></thead><tbody>{p.data.map(r=><WorkshopOrderRow key={r.id} row={r} navigate={navigate}/>)}</tbody></table></div>{p.data.length===0&&<p>Nenhuma ordem corresponde aos filtros e à atribuição autorizada.</p>}<button disabled={loading||p.current_page<=1} onClick={()=>setQuery(q=>({...q,page:q.page-1}))}>Anterior</button><button disabled={loading||p.current_page>=p.last_page} onClick={()=>setQuery(q=>({...q,page:q.page+1}))}>Seguinte</button><button disabled={loading} onClick={exportPage}>Exportar apenas esta página CSV</button></>}
<p className="help">Versões substituídas não contam como apontamentos actuais. “Sem verificação favorável” inclui pendentes, recusados e ocorrências reconhecidas; não é apenas uma fila de decisões por tomar. Sem totais de materiais ou custos entre unidades diferentes. A consulta não bloqueia a empresa para confirmação nem altera as ordens.</p></section>;
}