39 lines
4.3 KiB
React
39 lines
4.3 KiB
React
import React,{useEffect,useRef,useState} from 'react';
|
|
|
|
const labels={items:'Artigos',warehouses:'Armazéns',balances:'Existências físicas',movements:'Movimentos'};
|
|
export function milliQuantity(value) {
|
|
if(value===null||value===undefined)return 'Indeterminado';
|
|
const n=BigInt(value),negative=n<0n,absolute=negative?-n:n;
|
|
return `${negative?'-':''}${absolute/1000n},${String(absolute%1000n).padStart(3,'0')}`;
|
|
}
|
|
export default function StockReadWorkspace({api,context}) {
|
|
const initial={view:'balances',page:1,per_page:25,search:''};
|
|
const [query,setQuery]=useState(initial),[data,setData]=useState(null),[error,setError]=useState(''),[loading,setLoading]=useState(false);
|
|
const generation=useRef(0);
|
|
useEffect(()=>{
|
|
const current=++generation.current;setData(null);setError('');setLoading(true);
|
|
const params=new URLSearchParams(Object.entries(query).filter(([,v])=>v!==undefined&&v!==''));
|
|
api(`/api/stock-read?${params}`).then(r=>{if(current===generation.current)setData(r);})
|
|
.catch(e=>{if(current===generation.current)setError(e.message);}).finally(()=>{if(current===generation.current)setLoading(false);});
|
|
return ()=>{generation.current++;};
|
|
},[context.token,query]);
|
|
function filter(e){e.preventDefault();const f=Object.fromEntries(new FormData(e.currentTarget));setQuery({view:query.view,page:1,per_page:Number(f.per_page),search:f.search,warehouse_id:f.warehouse_id||undefined});}
|
|
function page(n){setQuery({...query,page:n,snapshot:data.snapshot});}
|
|
return <section className="panel"><h2>Consulta de Stock</h2>
|
|
<p>Somente leitura. As restantes operações de Stock continuam indisponíveis neste percurso.</p>
|
|
<nav aria-label="Consultas de stock">{Object.entries(labels).map(([id,label])=><button key={id} disabled={loading} aria-current={query.view===id?'page':undefined} onClick={()=>setQuery({...initial,view:id})}>{label}</button>)}</nav>
|
|
<form onSubmit={filter} key={query.view}><label>Pesquisa por código ou nome<input name="search" maxLength={100}/></label>
|
|
{['balances','movements'].includes(query.view)&&<label>Identificador do armazém autorizado<input name="warehouse_id" type="number" min="1"/></label>}
|
|
<label>Registos por página<select name="per_page" defaultValue="25"><option value="25">25</option><option value="100">100</option></select></label>
|
|
<button disabled={loading}>Pesquisar</button></form>
|
|
{loading&&<p role="status">A consultar a fonte autorizada…</p>}
|
|
{error&&<div role="alert"><p>{error}</p><button onClick={()=>setQuery({...query,page:1,snapshot:undefined})}>Actualizar consulta</button></div>}
|
|
{data&&<><p role="status">{data.notice}</p><p>Referência de leitura: {data.observed_at}. {data.pagination.total} registos; página {data.pagination.page} de {data.pagination.last_page}.</p>
|
|
<div className="table-wrap"><table><thead><tr><th>Código / origem</th><th>Designação</th>{query.view!=='warehouses'&&<th>Unidade</th>}{['balances','movements'].includes(query.view)&&<><th>Armazém</th><th>{query.view==='balances'?'Físico':'Quantidade'}</th><th>{query.view==='balances'?'Disponível':'Tipo / data'}</th></>}</tr></thead>
|
|
<tbody>{data.rows.map(row=><tr key={row.id??`${row.stock_item_id}:${row.warehouse_id}`}><td>#{row.id??row.stock_item_id} · {row.code}{row.source_reference&&<small> · {row.source_reference}</small>}</td><td>{row.name}</td>{query.view!=='warehouses'&&<td>{row.unit}</td>}{['balances','movements'].includes(query.view)&&<><td>#{row.warehouse_id} · {row.warehouse_code} · {row.warehouse_name}</td><td>{milliQuantity(query.view==='balances'?row.physical_milli:row.quantity_milli)}</td><td>{query.view==='balances'?'Indeterminado':`${row.movement_type === 'workshop_return_in' ? 'Devolução de excedente' : row.movement_type} · ${row.moved_at}`}</td></>}</tr>)}</tbody></table></div>
|
|
{data.rows.length===0&&<p>Sem registos elegíveis para estes filtros nesta fonte autorizada.</p>}
|
|
<button disabled={loading||query.page<=1} onClick={()=>page(query.page-1)}>Anterior</button><button disabled={loading||query.page>=data.pagination.last_page} onClick={()=>page(query.page+1)}>Seguinte</button>
|
|
<button disabled={loading} onClick={()=>setQuery({...query,page:1,snapshot:undefined})}>Nova referência de leitura</button></>}
|
|
</section>;
|
|
}
|