Clarify supply execution states
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m47s

This commit is contained in:
Cauê Faleiros
2026-08-03 12:57:43 -03:00
parent 0ff267be2c
commit f69efb5358
3 changed files with 85 additions and 32 deletions

View File

@@ -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