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

@@ -1838,6 +1838,14 @@ const getNeedPendingLabel = (need: SupplyPurchaseNeed) => {
return /^Cadastrar rendimento:/i.test(need.material) ? 'Rendimento' : 'Referência';
};
const hasNegativeMaterialBalance = (need: SupplyPurchaseNeed) => need.stockKg < 0;
const getNeedStatusLabel = (need: SupplyPurchaseNeed) => {
if (hasNegativeMaterialBalance(need)) return 'Saldo negativo';
if (need.missingReference) return 'Sem referência';
return purchaseStatusLabels[need.status];
};
type PurchaseNeedTab = 'materials' | 'services' | 'mapping';
const sumNeedProducts = (
@@ -1887,7 +1895,13 @@ const PurchaseNeedsScreen = () => {
const materialNeeds = summary.purchaseNeeds.filter(need => !need.missingReference && !isServicePurchaseNeed(need));
const serviceNeeds = summary.purchaseNeeds.filter(need => !need.missingReference && isServicePurchaseNeed(need));
const mappingNeeds = summary.purchaseNeeds.filter(need => need.missingReference);
const activeNeeds = activeTab === 'materials' ? materialNeeds : activeTab === 'services' ? serviceNeeds : mappingNeeds;
const activeNeeds = activeTab === 'materials'
? [...materialNeeds].sort((a, b) => {
if (hasNegativeMaterialBalance(a) !== hasNegativeMaterialBalance(b)) return hasNegativeMaterialBalance(a) ? -1 : 1;
if (a.status !== b.status) return a.status === 'critical' ? -1 : b.status === 'critical' ? 1 : 0;
return b.purchaseKg - a.purchaseKg;
})
: activeTab === 'services' ? serviceNeeds : mappingNeeds;
const normalizedSearch = normalizeSearch(search);
const visibleNeeds = activeNeeds.filter(need => (
!normalizedSearch || normalizeSearch(`${need.material} ${getNeedDisplayName(need)} ${need.suppliers.join(' ')} ${need.colors.join(' ')} ${(need.products || []).map(product => `${product.productId} ${product.name}`).join(' ')}`).includes(normalizedSearch)
@@ -1908,7 +1922,7 @@ const PurchaseNeedsScreen = () => {
? 'Necessidades de beneficiamento e serviços. Não entram no total de compra de materiais.'
: 'SKUs sem consumo confiável. Corrija o cadastro antes de usar estes valores para comprar.';
const stats = [
{ label: 'Materiais a comprar', value: `${purchaseItemCount}` },
{ label: 'Itens para comprar', value: `${purchaseItemCount} materiais` },
{ label: 'Compra sugerida', value: suggestedPurchaseSummary },
{ label: 'Serviços a revisar', value: `${serviceNeeds.filter(need => need.purchaseKg > 0).length}` },
{ label: 'SKUs sem mapeamento', value: `${impactedSkuCount || mappingNeeds.length}` },
@@ -1969,9 +1983,9 @@ const PurchaseNeedsScreen = () => {
</label>
<div className="mt-4 inline-flex rounded-xl border border-dark-border bg-dark-input p-1">
{[
{ id: 'materials' as const, label: `Materiais (${materialNeeds.length})` },
{ id: 'materials' as const, label: `Materiais (${materialNeeds.length} linhas)` },
{ id: 'services' as const, label: `Serviços (${serviceNeeds.length})` },
{ id: 'mapping' as const, label: `Mapear dados (${mappingNeeds.length})` },
{ id: 'mapping' as const, label: `Mapear dados (${impactedSkuCount || mappingNeeds.length} SKUs)` },
].map(tab => (
<button key={tab.id} type="button" onClick={() => { setActiveTab(tab.id); setSearch(''); setCurrentPage(1); }} className={`rounded-lg px-3 py-2 text-xs font-bold transition-colors cursor-pointer ${activeTab === tab.id ? 'bg-brand-primary text-brand-contrast' : 'text-dark-muted hover:text-dark-text'}`}>
{tab.label}
@@ -2070,7 +2084,7 @@ const PurchaseNeedsScreen = () => {
<th scope="col" className="px-4 py-3 text-right">{activeTab === 'services' ? 'Agendado' : 'Estoque atual'}</th>
<th scope="col" className="px-4 py-3 text-right">{activeTab === 'services' ? 'Em aberto' : 'Pendente'}</th>
<th scope="col" className="px-4 py-3 text-right">{activeTab === 'services' ? 'Contratar' : 'Comprar'}</th>
<th scope="col" className="px-4 py-3 text-left">{activeTab === 'services' ? 'Ação' : 'Cobertura'}</th>
<th scope="col" className="px-4 py-3 text-left">Situação</th>
</tr>
</thead>
<tbody className="divide-y divide-dark-border bg-dark-card">
@@ -2083,7 +2097,10 @@ const PurchaseNeedsScreen = () => {
<div>
<p className="text-sm font-bold text-dark-text">{need.products.length} {need.products.length === 1 ? 'SKU impactado' : 'SKUs impactados'}</p>
<p className="mt-1 text-xs font-semibold text-dark-muted">Demanda: {formatNumber(sumNeedProducts(need, 'suggestedQuantity'))} un.</p>
<p className="mt-1 truncate font-mono text-[10px] text-dark-muted" title={need.products.map(product => product.productId).join(', ')}>{need.products.slice(0, 3).map(product => product.productId).join(', ')}{need.products.length > 3 ? ` +${need.products.length - 3}` : ''}</p>
<p className="mt-1 flex flex-wrap gap-x-1.5 gap-y-1 font-mono text-[10px] text-dark-muted">
{need.products.slice(0, 3).map(product => <RouterLink key={product.productId} to={`/products/${product.productId}`} className="hover:text-brand-primary hover:underline">#{product.productId}</RouterLink>)}
{need.products.length > 3 && <span>+{need.products.length - 3}</span>}
</p>
</div>
) : <span className="text-xs font-semibold text-dark-muted">Plano manual</span>}
</td>
@@ -2110,13 +2127,15 @@ const PurchaseNeedsScreen = () => {
</div>
</td>
<td className="px-4 py-4 text-right align-middle text-sm font-bold text-dark-text">{formatNeedQuantity(need, need.plannedKg)}</td>
<td className="px-4 py-4 text-right align-middle text-sm font-bold text-dark-text">{activeTab === 'services' ? '—' : formatNeedQuantity(need, need.stockKg)}</td>
<td className={`px-4 py-4 text-right align-middle text-sm font-bold ${hasNegativeMaterialBalance(need) ? 'text-red-300' : 'text-dark-text'}`}>{activeTab === 'services' ? '—' : formatNeedQuantity(need, need.stockKg)}</td>
<td className="px-4 py-4 text-right align-middle text-sm font-bold text-dark-text">{activeTab === 'services' ? '—' : formatNeedQuantity(need, need.pendingKg)}</td>
<td className={`px-4 py-4 text-right align-middle text-sm font-bold ${need.purchaseKg > 0 ? 'text-amber-300' : 'text-emerald-300'}`}>{formatNeedQuantity(need, need.purchaseKg)}</td>
<td className="px-4 py-4 align-middle">
<span className={`inline-flex whitespace-nowrap rounded-full border px-2.5 py-1 text-xs font-bold ${
activeTab === 'services'
? 'border-sky-400/30 bg-sky-400/10 text-sky-300'
: hasNegativeMaterialBalance(need)
? 'border-red-400/30 bg-red-400/10 text-red-300'
: need.missingReference
? 'border-red-400/30 bg-red-400/10 text-red-300'
: need.status === 'critical'
@@ -2125,7 +2144,7 @@ const PurchaseNeedsScreen = () => {
? 'border-amber-400/30 bg-amber-400/10 text-amber-300'
: 'border-emerald-400/30 bg-emerald-400/10 text-emerald-300'
}`}>
{activeTab === 'services' ? 'Programar serviço' : need.missingReference ? 'Sem referência' : purchaseStatusLabels[need.status]}
{activeTab === 'services' ? 'Programar serviço' : getNeedStatusLabel(need)}
</span>
</td>
</tr>