25 lines
8.1 KiB
React
25 lines
8.1 KiB
React
import React,{useEffect,useMemo,useState}from'react';
|
|
const WorkshopOrderList=React.lazy(()=>import('./WorkshopOrderList.jsx'));
|
|
import{WorkshopStockOperation}from'./workshop-stock-operation.js';
|
|
const params=value=>new URLSearchParams(Object.fromEntries(Object.entries(value).filter(([,v])=>v!==''&&v!==null))).toString();
|
|
export function EquipmentPicker({api,context,clientId,disabled}){
|
|
const[query,setQuery]=useState({search:'',page:1,per_page:25}),[search,setSearch]=useState(''),[data,setData]=useState(null),[selected,setSelected]=useState(''),[error,setError]=useState('');
|
|
useEffect(()=>{setSelected('');setData(null);},[clientId,context.token]);useEffect(()=>{if(!clientId)return;let active=true;setError('');api('/api/workshop-equipment?'+params({...query,client_id:clientId})).then(r=>{if(active)setData(r.records);}).catch(e=>{if(active)setError(e.message);});return()=>{active=false;};},[clientId,context.token,query]);
|
|
return <fieldset disabled={disabled||!clientId}><label>Pesquisar equipamento do cliente<input value={search} onChange={e=>setSearch(e.target.value)}/></label><button type="button" onClick={()=>{setSelected('');setQuery({...query,search,page:1});}}>Pesquisar equipamento</button>{error&&<p role="alert">{error}</p>}<label>Equipamento existente<select name="equipment_id" required value={selected} onChange={e=>setSelected(e.target.value)}><option value="">Escolher equipamento…</option>{data?.data.map(e=><option key={e.id} value={e.id}>{e.identifier} · {e.description} · {e.serial??'sem série'}</option>)}</select></label>{data&&<><button type="button" disabled={data.current_page<=1} onClick={()=>{setSelected('');setQuery({...query,page:query.page-1});}}>Anterior</button><button type="button" disabled={data.current_page>=data.last_page} onClick={()=>{setSelected('');setQuery({...query,page:query.page+1});}}>Seguinte</button></>}</fieldset>;
|
|
}
|
|
export default function WorkshopEquipmentWorkspace({api,context,can,navigate}){
|
|
const [selectedEquipment,setSelectedEquipment]=useState(null);
|
|
const[query,setQuery]=useState({search:'',page:1,per_page:25}),[search,setSearch]=useState(''),[data,setData]=useState(null),[clients,setClients]=useState(null),[clientQuery,setClientQuery]=useState({search:'',page:1,per_page:25}),[clientSearch,setClientSearch]=useState(''),[form,setForm]=useState(null),[preview,setPreview]=useState(null),[busy,setBusy]=useState(false),[error,setError]=useState(''),[notice,setNotice]=useState('');const command=useMemo(()=>new WorkshopStockOperation(api),[context.token]),create=can('workshop.open',context.unit?.id),locked=busy||command.uncertain;
|
|
const load=async()=>setData((await api('/api/workshop-equipment?'+params(query))).records);
|
|
useEffect(()=>{let active=true;api('/api/workshop-equipment?'+params(query)).then(r=>{if(active)setData(r.records);}).catch(e=>{if(active)setError(e.message);});return()=>{active=false;};},[context.token,query]);
|
|
useEffect(()=>{if(!create)return;let active=true;api('/api/workshop-equipment?'+params({view:'clients',...clientQuery})).then(r=>{if(active)setClients(r.records);}).catch(e=>{if(active)setError(e.message);});return()=>{active=false;};},[context.token,clientQuery,create]);
|
|
const prepare=async e=>{e.preventDefault();setBusy(true);setError('');try{setPreview(await api('/api/workshop-equipment/preview','POST',{...form,client_id:Number(form.client_id)},undefined,crypto.randomUUID()));}catch(e){setError(e.message);}finally{setBusy(false);}};
|
|
const confirm=async retry=>{setBusy(true);setError('');try{const r=retry?await command.retry():await command.send('/api/workshop-equipment/confirm',{preview:preview.preview},crypto.randomUUID());setNotice(`Equipamento registado${r.replayed?' · recibo recuperado':''}. Pode ser escolhido numa nova ordem.`);setForm(null);setPreview(null);await load();}catch(e){setError(e.message);if(!command.uncertain)setPreview(null);}finally{setBusy(false);}};
|
|
if(selectedEquipment)return <React.Suspense fallback={<p>A abrir ordens do equipamento…</p>}><WorkshopOrderList key={context.token+':'+selectedEquipment.id} context={context} api={api} navigate={navigate} equipmentId={selectedEquipment.id} onBack={()=>setSelectedEquipment(null)}/></React.Suspense>;
|
|
return <section className="panel" data-sgf-operation-uncertain={command.uncertain||preview?'true':undefined}><h2>Equipamentos do módulo</h2><p>Cadastro individual por cliente, código, descrição/marca/modelo e série. Reutilize a identificação ao abrir outra ordem. As identificações existentes permanecem imutáveis.</p>{error&&<p role="alert">{error}</p>}{notice&&<p role="status">{notice}</p>}<form onSubmit={e=>{e.preventDefault();setQuery({...query,search,page:1});}}><fieldset disabled={locked}><label>Pesquisar código, descrição ou série<input value={search} onChange={e=>setSearch(e.target.value)}/></label><label>Por página<select value={query.per_page} onChange={e=>setQuery({...query,per_page:Number(e.target.value),page:1})}><option value="25">25</option><option value="100">100</option></select></label><button>Pesquisar</button></fieldset></form>
|
|
{create&&<button disabled={locked||Boolean(form)} onClick={()=>setForm({client_id:'',identifier:'',description:'',serial:'',reason:''})}>Registar equipamento</button>}{navigate&&<button disabled={locked||Boolean(preview)} onClick={()=>navigate('workshop')}>Abrir ordens e reutilizar equipamento</button>}
|
|
{data&&<><div className="table-scroll"><table><thead><tr><th>Código</th><th>Descrição</th><th>Série</th><th>Cliente</th><th>Ordens</th></tr></thead><tbody>{data.data.map(e=><tr key={e.id}><td>{e.identifier}</td><td>{e.description}</td><td>{e.serial??'Não registada'}</td><td>{e.client_name}</td><td><button disabled={locked||Boolean(form)||Boolean(preview)} onClick={()=>setSelectedEquipment(e)}>Consultar ordens deste equipamento</button></td></tr>)}</tbody></table></div>{!data.data.length&&<p>Sem equipamentos visíveis para esta pesquisa.</p>}<button disabled={locked||data.current_page<=1} onClick={()=>setQuery({...query,page:query.page-1})}>Anterior</button><span> Página {data.current_page}/{data.last_page} </span><button disabled={locked||data.current_page>=data.last_page} onClick={()=>setQuery({...query,page:query.page+1})}>Seguinte</button></>}
|
|
{form&&<section className="panel"><h3>Nova identificação</h3><fieldset disabled={locked||Boolean(preview)}><label>Pesquisar cliente<input value={clientSearch} onChange={e=>setClientSearch(e.target.value)}/></label><button type="button" onClick={()=>setClientQuery({...clientQuery,search:clientSearch,page:1})}>Pesquisar cliente</button>{clients&&<><button type="button" disabled={clients.current_page<=1} onClick={()=>setClientQuery({...clientQuery,page:clientQuery.page-1})}>Anterior</button><button type="button" disabled={clients.current_page>=clients.last_page} onClick={()=>setClientQuery({...clientQuery,page:clientQuery.page+1})}>Seguinte</button></>}</fieldset><form onSubmit={prepare}><fieldset disabled={locked||Boolean(preview)}><label>Cliente<select required value={form.client_id} onChange={e=>setForm({...form,client_id:e.target.value})}><option value="">Escolher cliente…</option>{clients?.data.map(c=><option key={c.id} value={c.id}>{c.name}</option>)}</select></label>{[['identifier','Código individual'],['description','Descrição / marca / modelo'],['serial','Série (opcional)'],['reason','Fundamento']].map(([key,label])=><label key={key}>{label}<input required={key!=='serial'} minLength={key==='reason'?10:undefined} maxLength={key==='description'?300:key==='reason'?2000:100} value={form[key]} onChange={e=>{setPreview(null);setForm({...form,[key]:e.target.value});}}/></label>)}<button>Conferir identificação</button></fieldset></form>{preview&&<div role="status"><p>Registar {preview.impact.identifier} · {preview.impact.description} para {preview.impact.client}. Não abre ordem nem cria efeitos físicos/financeiros.</p><button disabled={locked} onClick={()=>confirm(false)}>Confirmar registo</button></div>}{command.uncertain&&<button disabled={busy} onClick={()=>confirm(true)}>Recuperar confirmação</button>}<button disabled={locked} onClick={()=>{setForm(null);setPreview(null);}}>Descartar</button></section>}
|
|
</section>;
|
|
}
|