Clarify supply execution states
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m47s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m47s
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Link, useOutletContext, useSearchParams } from 'react-router-dom';
|
||||
import { AlertTriangle, ArrowLeft, ClipboardList, Download, Eye, Layers3, Palette, Pencil, RotateCcw, Ruler, Save as SaveIcon, Scissors, Search, Settings2, X } from 'lucide-react';
|
||||
import { AlertTriangle, ArrowLeft, ClipboardList, Download, Eye, Layers3, Pencil, RotateCcw, Ruler, Save as SaveIcon, Scissors, Search, Settings2, X } from 'lucide-react';
|
||||
import DateRangePicker from '../components/DateRangePicker';
|
||||
import PaginationControls from '../components/PaginationControls';
|
||||
import ProductColorBadge from '../components/ProductColorBadge';
|
||||
@@ -327,6 +327,24 @@ const Cutting = () => {
|
||||
filteredRows.filter(row => row.suggestedCutQuantity > 0 && materialReadinessByProductId.get(row.id)?.status === 'ready')
|
||||
), [filteredRows, materialReadinessByProductId]);
|
||||
|
||||
const cutExecutionSummary = useMemo(() => {
|
||||
const rowsWithNeed = cutPlan.needRows.filter(row => row.suggestedCutQuantity > 0);
|
||||
const readyRows = rowsWithNeed.filter(row => materialReadinessByProductId.get(row.id)?.status === 'ready');
|
||||
const blockedByMaterial = rowsWithNeed.filter(row => {
|
||||
const status = materialReadinessByProductId.get(row.id)?.status;
|
||||
return status === 'blocked' || status === 'missing';
|
||||
});
|
||||
const blockedByData = rowsWithNeed.filter(row => materialReadinessByProductId.get(row.id)?.status === 'material_ready');
|
||||
return {
|
||||
rawUnits: rowsWithNeed.reduce((total, row) => total + row.suggestedCutQuantity, 0),
|
||||
readyUnits: readyRows.reduce((total, row) => total + row.suggestedCutQuantity, 0),
|
||||
readyRows: readyRows.length,
|
||||
blockedRows: blockedByMaterial.length + blockedByData.length,
|
||||
blockedByMaterial: blockedByMaterial.length,
|
||||
blockedByData: blockedByData.length,
|
||||
};
|
||||
}, [cutPlan.needRows, materialReadinessByProductId]);
|
||||
|
||||
const orderPreviewTotalUnits = useMemo(() => (
|
||||
rowsAvailableForOrder.reduce((total, row) => total + row.suggestedCutQuantity, 0)
|
||||
), [rowsAvailableForOrder]);
|
||||
@@ -1007,8 +1025,8 @@ const Cutting = () => {
|
||||
<div className="rounded-2xl border border-dark-border bg-dark-card p-5 shadow-sm">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<p className="text-xs font-bold uppercase tracking-widest text-dark-muted">Unidades a cortar</p>
|
||||
<p className="mt-2 text-3xl font-bold text-red-300">{formatNumber(cutPlan.summary.suggestedCutQuantity)}</p>
|
||||
<p className="text-xs font-bold uppercase tracking-widest text-dark-muted">Necessidade bruta</p>
|
||||
<p className="mt-2 text-3xl font-bold text-red-300">{formatNumber(cutExecutionSummary.rawUnits)}</p>
|
||||
<p className="mt-1 text-xs font-semibold text-dark-muted">{formatNumber(cutPlan.summary.skuCount)} SKUs com necessidade</p>
|
||||
</div>
|
||||
<div className="rounded-xl border border-red-400/25 bg-red-400/10 p-3 text-red-300">
|
||||
@@ -1020,11 +1038,9 @@ const Cutting = () => {
|
||||
<div className="rounded-2xl border border-dark-border bg-dark-card p-5 shadow-sm">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<p className="text-xs font-bold uppercase tracking-widest text-dark-muted">Famílias</p>
|
||||
<p className="mt-2 text-3xl font-bold text-dark-text">{formatNumber(cutPlan.summary.familiesWithNeed)}</p>
|
||||
<p className="mt-1 text-xs font-semibold text-dark-muted">
|
||||
{cutPlan.summary.estimatedRolls === null ? 'Rolos pendentes de rendimento' : `${formatNumber(cutPlan.summary.estimatedRolls)} rolos estimados`}
|
||||
</p>
|
||||
<p className="text-xs font-bold uppercase tracking-widest text-dark-muted">Pronto para corte</p>
|
||||
<p className="mt-2 text-3xl font-bold text-emerald-300">{formatNumber(cutExecutionSummary.readyUnits)}</p>
|
||||
<p className="mt-1 text-xs font-semibold text-dark-muted">{formatNumber(cutExecutionSummary.readyRows)} SKUs liberados para gerar OP</p>
|
||||
</div>
|
||||
<div className="rounded-xl border border-brand-primary/25 bg-brand-primary/10 p-3 text-brand-primary">
|
||||
<Layers3 className="h-5 w-5" />
|
||||
@@ -1035,14 +1051,12 @@ const Cutting = () => {
|
||||
<div className="rounded-2xl border border-dark-border bg-dark-card p-5 shadow-sm">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<p className="text-xs font-bold uppercase tracking-widest text-dark-muted">Cores / tamanhos</p>
|
||||
<p className="mt-2 text-3xl font-bold text-sky-300">
|
||||
{formatNumber(cutPlan.summary.colorsWithNeed)} / {formatNumber(cutPlan.summary.sizesWithNeed)}
|
||||
</p>
|
||||
<p className="mt-1 text-xs font-semibold text-dark-muted">Com corte sugerido</p>
|
||||
<p className="text-xs font-bold uppercase tracking-widest text-dark-muted">Bloqueios</p>
|
||||
<p className="mt-2 text-3xl font-bold text-amber-300">{formatNumber(cutExecutionSummary.blockedRows)}</p>
|
||||
<p className="mt-1 text-xs font-semibold text-dark-muted">{formatNumber(cutExecutionSummary.blockedByMaterial)} material · {formatNumber(cutExecutionSummary.blockedByData)} dados</p>
|
||||
</div>
|
||||
<div className="rounded-xl border border-sky-400/25 bg-sky-400/10 p-3 text-sky-300">
|
||||
<Palette className="h-5 w-5" />
|
||||
<div className="rounded-xl border border-amber-400/25 bg-amber-400/10 p-3 text-amber-300">
|
||||
<AlertTriangle className="h-5 w-5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1050,17 +1064,27 @@ const Cutting = () => {
|
||||
<div className="rounded-2xl border border-dark-border bg-dark-card p-5 shadow-sm">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<p className="text-xs font-bold uppercase tracking-widest text-dark-muted">OP aberta</p>
|
||||
<p className="mt-2 text-3xl font-bold text-amber-300">{formatNumber(cutPlan.summary.openProductionQuantity)}</p>
|
||||
<p className="mt-1 text-xs font-semibold text-dark-muted">Unidades abatidas quando há match</p>
|
||||
<p className="text-xs font-bold uppercase tracking-widest text-dark-muted">Famílias</p>
|
||||
<p className="mt-2 text-3xl font-bold text-brand-primary">{formatNumber(cutPlan.summary.familiesWithNeed)}</p>
|
||||
<p className="mt-1 text-xs font-semibold text-dark-muted">{cutPlan.summary.estimatedRolls === null ? 'Rendimento ainda não configurado' : `${formatNumber(cutPlan.summary.estimatedRolls)} rolos estimados`}</p>
|
||||
</div>
|
||||
<div className="rounded-xl border border-amber-400/25 bg-amber-400/10 p-3 text-amber-300">
|
||||
<ClipboardList className="h-5 w-5" />
|
||||
<div className="rounded-xl border border-brand-primary/25 bg-brand-primary/10 p-3 text-brand-primary">
|
||||
<Layers3 className="h-5 w-5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{cutExecutionSummary.rawUnits > 0 && cutExecutionSummary.readyUnits === 0 && (
|
||||
<div className="flex flex-col gap-3 rounded-2xl border border-amber-400/30 bg-amber-400/10 p-4 md:flex-row md:items-center md:justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-bold text-amber-300">Há demanda, mas nenhum SKU está liberado para corte.</p>
|
||||
<p className="mt-1 text-xs font-semibold text-dark-muted">Corrija materiais bloqueados e rendimentos antes de gerar ordens de produção.</p>
|
||||
</div>
|
||||
<button type="button" onClick={() => setActiveCuttingView('issues')} className="h-9 rounded-lg border border-amber-400/30 bg-dark-card px-3 text-xs font-bold text-amber-300 hover:border-amber-400 cursor-pointer">Ver pendências</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="rounded-2xl border border-dark-border bg-dark-card p-2 shadow-sm">
|
||||
<div className="grid grid-cols-1 gap-2 md:grid-cols-3">
|
||||
<button
|
||||
@@ -1072,11 +1096,11 @@ const Cutting = () => {
|
||||
<span className="flex items-center gap-3">
|
||||
<Scissors className="h-4 w-4" />
|
||||
<span>
|
||||
<span className="block text-sm font-bold">Plano</span>
|
||||
<span className="block text-xs font-semibold opacity-80">SKUs para cortar</span>
|
||||
<span className="block text-sm font-bold">Necessidades</span>
|
||||
<span className="block text-xs font-semibold opacity-80">Demanda por SKU</span>
|
||||
</span>
|
||||
</span>
|
||||
<span className="text-sm font-bold">{formatNumber(rowsAvailableForOrder.length)}</span>
|
||||
<span className="text-sm font-bold">{formatNumber(cutPlan.summary.skuCount)}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user