Simplify production order preview
This commit is contained in:
@@ -168,6 +168,7 @@ const Cutting = () => {
|
||||
const [selectedPreviewRowIds, setSelectedPreviewRowIds] = useState<string[]>([]);
|
||||
const [lotSelections, setLotSelections] = useState<Record<string, { lotId: string; quantity: string }>>({});
|
||||
const [productionPlanForm, setProductionPlanForm] = useState({ name: '', contractor: '', expectedDate: '', notes: '' });
|
||||
const [isPlanDetailsOpen, setIsPlanDetailsOpen] = useState(false);
|
||||
const [editingCutRow, setEditingCutRow] = useState<CutPlanSkuRow | null>(null);
|
||||
const [quickCutEdit, setQuickCutEdit] = useState<CutProductOverride>({});
|
||||
const [isQuickCutEditSaving, setIsQuickCutEditSaving] = useState(false);
|
||||
@@ -668,6 +669,7 @@ const Cutting = () => {
|
||||
setSelectedPreviewRowIds(rowsAvailableForOrder.map(row => row.id));
|
||||
setLotSelections({});
|
||||
setProductionPlanForm({ name: cutPlanReference, contractor: '', expectedDate: '', notes: '' });
|
||||
setIsPlanDetailsOpen(false);
|
||||
setIsOrderPreviewOpen(true);
|
||||
};
|
||||
|
||||
@@ -900,34 +902,30 @@ const Cutting = () => {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 divide-x divide-y divide-dark-border border-b border-dark-border md:grid-cols-4 md:divide-y-0">
|
||||
{[
|
||||
['Selecionadas', `${formatNumber(selectedPreviewRows.length)} de ${formatNumber(rowsAvailableForOrder.length)}`, 'text-dark-text'],
|
||||
['Unidades selecionadas', `${formatNumber(orderPreviewTotalUnits)} un.`, 'text-brand-primary'],
|
||||
['Cobertura alvo', `${formatNumber(targetCoverageDays)} dias`, 'text-dark-text'],
|
||||
['Revisar dados', formatNumber(previewRowsWithIssues), previewRowsWithIssues ? 'text-amber-300' : 'text-emerald-300']
|
||||
].map(([label, value, tone]) => <div key={label} className="p-4"><p className="text-[10px] font-bold uppercase tracking-widest text-dark-muted">{label}</p><p className={`mt-1 text-xl font-bold ${tone}`}>{value}</p></div>)}
|
||||
<div className="flex flex-col gap-3 border-b border-dark-border bg-dark-input/25 px-5 py-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex flex-wrap items-center gap-2 text-sm font-bold text-dark-text">
|
||||
<span>{formatNumber(selectedPreviewRows.length)} selecionada{selectedPreviewRows.length === 1 ? '' : 's'}</span>
|
||||
<span className="text-dark-muted">·</span>
|
||||
<span className="text-brand-primary">{formatNumber(orderPreviewTotalUnits)} un.</span>
|
||||
{previewRowsWithIssues > 0 && <><span className="text-dark-muted">·</span><span className="text-amber-300">{formatNumber(previewRowsWithIssues)} para revisar</span></>}
|
||||
</div>
|
||||
<span className="text-xs font-semibold text-dark-muted">Cobertura de {formatNumber(targetCoverageDays)} dias</span>
|
||||
</div>
|
||||
|
||||
<div className="border-b border-dark-border bg-dark-input/25 p-4">
|
||||
<div className="mb-3">
|
||||
<p className="text-xs font-bold uppercase tracking-widest text-brand-primary">Plano de produção</p>
|
||||
<p className="mt-1 text-xs font-semibold text-dark-muted">Estas OPs serão gravadas dentro de um único plano PCP. O plano pode avançar todas as OPs ativas juntas.</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-4">
|
||||
<label className="text-xs font-bold text-dark-muted">Nome do plano
|
||||
<input value={productionPlanForm.name} onChange={(event) => setProductionPlanForm(current => ({ ...current, name: event.target.value }))} className="mt-1 h-10 w-full rounded-lg border border-dark-border bg-dark-card px-3 text-sm font-semibold text-dark-text outline-none focus:border-brand-primary" />
|
||||
</label>
|
||||
<label className="text-xs font-bold text-dark-muted">Facção / responsável
|
||||
<input value={productionPlanForm.contractor} onChange={(event) => setProductionPlanForm(current => ({ ...current, contractor: event.target.value }))} placeholder="Opcional" className="mt-1 h-10 w-full rounded-lg border border-dark-border bg-dark-card px-3 text-sm font-semibold text-dark-text outline-none placeholder:text-dark-muted focus:border-brand-primary" />
|
||||
</label>
|
||||
<label className="text-xs font-bold text-dark-muted">Previsão de conclusão
|
||||
<input type="date" value={productionPlanForm.expectedDate} onChange={(event) => setProductionPlanForm(current => ({ ...current, expectedDate: event.target.value }))} className="mt-1 h-10 w-full rounded-lg border border-dark-border bg-dark-card px-3 text-sm font-semibold text-dark-text outline-none focus:border-brand-primary" />
|
||||
</label>
|
||||
<label className="text-xs font-bold text-dark-muted">Observação operacional
|
||||
<input value={productionPlanForm.notes} onChange={(event) => setProductionPlanForm(current => ({ ...current, notes: event.target.value }))} placeholder="Lotes, prioridade, entrega..." className="mt-1 h-10 w-full rounded-lg border border-dark-border bg-dark-card px-3 text-sm font-semibold text-dark-text outline-none placeholder:text-dark-muted focus:border-brand-primary" />
|
||||
</label>
|
||||
<div className="border-b border-dark-border px-5 py-4">
|
||||
<div className="flex flex-col gap-3 md:flex-row md:items-end">
|
||||
<label className="block min-w-0 flex-1 text-xs font-bold text-dark-muted">Plano de produção
|
||||
<input value={productionPlanForm.name} onChange={(event) => setProductionPlanForm(current => ({ ...current, name: event.target.value }))} className="mt-1 h-10 w-full rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text outline-none focus:border-brand-primary" />
|
||||
</label>
|
||||
<button type="button" onClick={() => setIsPlanDetailsOpen(current => !current)} className="inline-flex h-10 shrink-0 items-center justify-center rounded-lg border border-dark-border bg-dark-card px-3 text-xs font-bold text-dark-muted transition-colors hover:border-brand-primary hover:text-dark-text cursor-pointer">
|
||||
{isPlanDetailsOpen ? 'Ocultar detalhes' : 'Adicionar detalhes'}
|
||||
</button>
|
||||
</div>
|
||||
{isPlanDetailsOpen && <div className="mt-3 grid grid-cols-1 gap-3 border-t border-dark-border pt-3 md:grid-cols-3">
|
||||
<label className="text-xs font-bold text-dark-muted">Facção / responsável<input value={productionPlanForm.contractor} onChange={(event) => setProductionPlanForm(current => ({ ...current, contractor: event.target.value }))} placeholder="Opcional" className="mt-1 h-10 w-full rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text outline-none placeholder:text-dark-muted focus:border-brand-primary" /></label>
|
||||
<label className="text-xs font-bold text-dark-muted">Previsão de conclusão<input type="date" value={productionPlanForm.expectedDate} onChange={(event) => setProductionPlanForm(current => ({ ...current, expectedDate: event.target.value }))} className="mt-1 h-10 w-full rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text outline-none focus:border-brand-primary" /></label>
|
||||
<label className="text-xs font-bold text-dark-muted">Observação<input value={productionPlanForm.notes} onChange={(event) => setProductionPlanForm(current => ({ ...current, notes: event.target.value }))} placeholder="Opcional" className="mt-1 h-10 w-full rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text outline-none placeholder:text-dark-muted focus:border-brand-primary" /></label>
|
||||
</div>}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3 border-b border-dark-border bg-dark-input/40 px-4 py-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
@@ -985,7 +983,7 @@ const Cutting = () => {
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<section className="border-t border-dark-border bg-dark-input/30 p-4">
|
||||
{selectedMaterialRequirements.length > 0 && <section className="border-t border-dark-border bg-dark-input/30 p-4">
|
||||
<div className="flex flex-col gap-1 sm:flex-row sm:items-end sm:justify-between">
|
||||
<div>
|
||||
<h3 className="text-sm font-bold text-dark-text">Reservar lotes para este corte</h3>
|
||||
@@ -994,7 +992,7 @@ const Cutting = () => {
|
||||
<span className="text-xs font-bold text-brand-primary">{formatNumber(selectedLotReservations.length)} lote(s) reservado(s)</span>
|
||||
</div>
|
||||
{selectedMaterialRequirements.length ? <div className="mt-4 overflow-x-auto rounded-xl border border-dark-border bg-dark-card"><table className="w-full min-w-[760px] text-left text-xs"><thead className="bg-dark-header text-dark-muted"><tr><th className="px-3 py-2.5 font-bold">Material necessário</th><th className="px-3 py-2.5 text-right font-bold">Necessário</th><th className="px-3 py-2.5 font-bold">Lote reservado</th><th className="px-3 py-2.5 text-right font-bold">Quantidade reservada</th></tr></thead><tbody className="divide-y divide-dark-border">{selectedMaterialRequirements.map(requirement => { const selection = lotSelections[requirement.key] || { lotId: '', quantity: '' }; const lots = supplyLots.filter(lot => { const lotKey = normalizeMaterialKey(lot.product); const materialMatch = lotKey === requirement.key || lotKey === normalizeMaterialKey(requirement.label); return lot.availableQuantity > 0 && materialMatch && lot.unit.toLowerCase() === requirement.unit.toLowerCase(); }); return <tr key={requirement.key}><td className="px-3 py-3"><p className="font-bold text-dark-text">{requirement.label}</p><p className="mt-0.5 font-mono text-[10px] text-dark-muted">{requirement.key}</p></td><td className="px-3 py-3 text-right font-bold text-dark-text">{formatNumber(requirement.quantity, 4)} {requirement.unit}</td><td className="px-3 py-3"><select value={selection.lotId} onChange={(event) => setLotSelections(current => ({ ...current, [requirement.key]: { lotId: event.target.value, quantity: current[requirement.key]?.quantity || '' } }))} className="h-9 w-full min-w-[230px] rounded-lg border border-dark-border bg-dark-input px-2 text-xs font-semibold text-dark-text outline-none focus:border-brand-primary"><option value="">{lots.length ? 'Não reservar agora' : 'Sem lote compatível'}</option>{lots.map(lot => <option key={lot.id} value={lot.id}>#{lot.id} · {lot.product} · disp. {formatNumber(lot.availableQuantity, 4)} {lot.unit}</option>)}</select></td><td className="px-3 py-3"><input inputMode="decimal" value={selection.quantity} onChange={(event) => setLotSelections(current => ({ ...current, [requirement.key]: { lotId: current[requirement.key]?.lotId || '', quantity: event.target.value } }))} disabled={!selection.lotId} className="h-9 w-32 rounded-lg border border-dark-border bg-dark-input px-2 text-right text-xs font-semibold text-dark-text outline-none placeholder:text-dark-muted focus:border-brand-primary disabled:cursor-not-allowed disabled:opacity-50" placeholder={formatNumber(requirement.quantity, 4)} /></td></tr>; })}</tbody></table></div> : <p className="mt-3 rounded-xl border border-dark-border bg-dark-card px-3 py-3 text-xs font-semibold text-dark-muted">Os SKUs selecionados não possuem componentes de material sincronizados. Eles podem seguir para OP, mas não geram reserva.</p>}
|
||||
</section>
|
||||
</section>}
|
||||
|
||||
<PaginationControls
|
||||
totalItems={rowsAvailableForOrder.length}
|
||||
|
||||
Reference in New Issue
Block a user