Link supply blockers to actionable queues
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { type FormEvent, useEffect, useMemo, useState } from 'react';
|
||||
import { Link as RouterLink, Navigate, useOutletContext, useParams } from 'react-router-dom';
|
||||
import { Link as RouterLink, Navigate, useOutletContext, useParams, useSearchParams } from 'react-router-dom';
|
||||
import {
|
||||
AlertTriangle,
|
||||
ArrowLeft,
|
||||
@@ -313,7 +313,7 @@ const SuppliesHub = () => {
|
||||
description: 'Estoque negativo distorce a compra sugerida e pode liberar corte sem material físico.',
|
||||
meta: 'Abrir compra de materiais',
|
||||
icon: AlertTriangle,
|
||||
to: '/supplies/purchase-needs',
|
||||
to: '/supplies/purchase-needs?tab=materials&filter=negative_balance',
|
||||
tone: 'critical' as const,
|
||||
}
|
||||
: mappingSkuCount > 0
|
||||
@@ -323,7 +323,7 @@ const SuppliesHub = () => {
|
||||
description: 'Sem composição confiável, a necessidade de compra e o corte não representam a demanda real.',
|
||||
meta: 'Abrir pendências de composição',
|
||||
icon: LinkIcon,
|
||||
to: '/supplies/purchase-needs',
|
||||
to: '/supplies/purchase-needs?tab=mapping',
|
||||
tone: 'info' as const,
|
||||
}
|
||||
: materialPurchaseNeeds.length > 0
|
||||
@@ -333,7 +333,7 @@ const SuppliesHub = () => {
|
||||
description: 'A necessidade já desconta saldo e recebimentos para apoiar a decisão de compra.',
|
||||
meta: 'Ver lista de compra',
|
||||
icon: BarChart3,
|
||||
to: '/supplies/purchase-needs',
|
||||
to: '/supplies/purchase-needs?tab=materials&filter=to_buy',
|
||||
tone: 'attention' as const,
|
||||
}
|
||||
: activeProductionCount > 0
|
||||
@@ -413,9 +413,9 @@ const SuppliesHub = () => {
|
||||
<p className="mt-1 text-sm font-semibold text-dark-muted">Resolva estes pontos antes de confiar no plano.</p>
|
||||
</div>
|
||||
<div className="mt-4 space-y-2">
|
||||
<FlowBlocker title="Saldos a conferir" description="Materiais com estoque negativo" count={negativeMaterialNeeds.length} to="/supplies/purchase-needs" tone="critical" />
|
||||
<FlowBlocker title="Dados para mapear" description="SKUs sem composição confiável" count={mappingSkuCount} to="/supplies/purchase-needs" tone="info" />
|
||||
<FlowBlocker title="Materiais sem saldo" description="Itens necessários sem disponibilidade" count={noStockMaterialNeeds.length} to="/supplies/purchase-needs" tone="attention" />
|
||||
<FlowBlocker title="Saldos a conferir" description="Materiais com estoque negativo" count={negativeMaterialNeeds.length} to="/supplies/purchase-needs?tab=materials&filter=negative_balance" tone="critical" />
|
||||
<FlowBlocker title="Dados para mapear" description="SKUs sem composição confiável" count={mappingSkuCount} to="/supplies/purchase-needs?tab=mapping" tone="info" />
|
||||
<FlowBlocker title="Materiais sem saldo" description="Itens necessários sem disponibilidade" count={noStockMaterialNeeds.length} to="/supplies/purchase-needs?tab=materials&filter=no_stock" tone="attention" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -425,8 +425,8 @@ const SuppliesHub = () => {
|
||||
<p className="mt-1 text-sm font-semibold text-dark-muted">Da necessidade de material até a produção acompanhada.</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4 xl:grid-cols-3">
|
||||
<FlowStepCard number="01" title="Garantir materiais" description="Confira saldos, pendências de composição e a compra sugerida." icon={BarChart3} to="/supplies/purchase-needs" tone={negativeMaterialNeeds.length > 0 ? 'critical' : materialPurchaseNeeds.length > 0 ? 'attention' : 'ready'} meta={negativeMaterialNeeds.length > 0 ? `${formatNumber(negativeMaterialNeeds.length, 0)} saldos negativos` : materialPurchaseNeeds.length > 0 ? `${formatNumber(materialPurchaseNeeds.length, 0)} materiais para comprar` : 'Materiais cobertos'} />
|
||||
<FlowStepCard number="02" title="Definir corte" description="Priorize SKUs, cores e tamanhos com necessidade e material disponível." icon={Scissors} to="/cutting" tone={mappingSkuCount > 0 ? 'attention' : 'default'} meta={mappingSkuCount > 0 ? `${formatNumber(mappingSkuCount, 0)} SKUs precisam de mapeamento` : 'Abrir plano de corte'} />
|
||||
<FlowStepCard number="01" title="Garantir materiais" description="Confira saldos, pendências de composição e a compra sugerida." icon={BarChart3} to={negativeMaterialNeeds.length > 0 ? '/supplies/purchase-needs?tab=materials&filter=negative_balance' : materialPurchaseNeeds.length > 0 ? '/supplies/purchase-needs?tab=materials&filter=to_buy' : mappingSkuCount > 0 ? '/supplies/purchase-needs?tab=mapping' : '/supplies/purchase-needs?tab=materials&filter=all'} tone={negativeMaterialNeeds.length > 0 ? 'critical' : materialPurchaseNeeds.length > 0 ? 'attention' : mappingSkuCount > 0 ? 'attention' : 'ready'} meta={negativeMaterialNeeds.length > 0 ? `${formatNumber(negativeMaterialNeeds.length, 0)} saldos negativos` : materialPurchaseNeeds.length > 0 ? `${formatNumber(materialPurchaseNeeds.length, 0)} materiais para comprar` : mappingSkuCount > 0 ? 'Sem falta calculada · dados pendentes' : 'Materiais cobertos'} />
|
||||
<FlowStepCard number="02" title="Definir corte" description="Priorize SKUs, cores e tamanhos com necessidade e material disponível." icon={Scissors} to={mappingSkuCount > 0 ? '/supplies/purchase-needs?tab=mapping' : '/cutting'} tone={mappingSkuCount > 0 ? 'attention' : 'default'} meta={mappingSkuCount > 0 ? `${formatNumber(mappingSkuCount, 0)} SKUs precisam de mapeamento` : 'Abrir plano de corte'} />
|
||||
<FlowStepCard number="03" title="Acompanhar produção" description="Gerencie as OPs criadas a partir do corte até sua finalização." icon={ClipboardList} to="/production-orders" tone={activeProductionCount > 0 ? 'ready' : 'default'} meta={activeProductionCount > 0 ? `${formatNumber(productionCounts.open, 0)} abertas · ${formatNumber(productionCounts.inProgress, 0)} em andamento` : 'Nenhuma OP em execução'} />
|
||||
</div>
|
||||
</section>
|
||||
@@ -2240,11 +2240,14 @@ const PurchaseMappingQueue = ({ needs }: { needs: SupplyPurchaseNeed[] }) => (
|
||||
);
|
||||
|
||||
const PurchaseNeedsScreen = () => {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [summary, setSummary] = useState<SupplySummary>(emptySupplySummary);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [search, setSearch] = useState('');
|
||||
const [activeTab, setActiveTab] = useState<PurchaseNeedTab>('materials');
|
||||
const [materialFilter, setMaterialFilter] = useState<MaterialPurchaseFilter>('to_buy');
|
||||
const initialTab = searchParams.get('tab');
|
||||
const initialFilter = searchParams.get('filter');
|
||||
const [activeTab, setActiveTab] = useState<PurchaseNeedTab>(initialTab === 'services' || initialTab === 'mapping' ? initialTab : 'materials');
|
||||
const [materialFilter, setMaterialFilter] = useState<MaterialPurchaseFilter>(initialFilter === 'negative_balance' || initialFilter === 'no_stock' || initialFilter === 'all' ? initialFilter : 'to_buy');
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [itemsPerPage, setItemsPerPage] = useState(10);
|
||||
|
||||
@@ -2273,6 +2276,23 @@ const PurchaseNeedsScreen = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const tab = searchParams.get('tab');
|
||||
const filter = searchParams.get('filter');
|
||||
setActiveTab(tab === 'services' || tab === 'mapping' ? tab : 'materials');
|
||||
setMaterialFilter(filter === 'negative_balance' || filter === 'no_stock' || filter === 'all' ? filter : 'to_buy');
|
||||
setSearch('');
|
||||
setCurrentPage(1);
|
||||
}, [searchParams]);
|
||||
|
||||
const openQueue = (tab: PurchaseNeedTab, filter: MaterialPurchaseFilter = 'to_buy') => {
|
||||
setActiveTab(tab);
|
||||
setMaterialFilter(filter);
|
||||
setSearch('');
|
||||
setCurrentPage(1);
|
||||
setSearchParams(tab === 'materials' ? { tab, filter } : { tab });
|
||||
};
|
||||
|
||||
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);
|
||||
@@ -2325,7 +2345,7 @@ const PurchaseNeedsScreen = () => {
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => { setActiveTab('materials'); setMaterialFilter('to_buy'); setSearch(''); setCurrentPage(1); }}
|
||||
onClick={() => openQueue('materials', 'to_buy')}
|
||||
className={`rounded-2xl border p-5 text-left shadow-sm transition-colors ${activeTab === 'materials' && materialFilter === 'to_buy' ? 'border-brand-primary/45 bg-brand-primary/[0.07]' : 'border-dark-border bg-dark-card hover:border-brand-primary/35'}`}
|
||||
>
|
||||
<p className="text-xs font-bold uppercase tracking-widest text-dark-muted">Materiais para comprar</p>
|
||||
@@ -2339,7 +2359,7 @@ const PurchaseNeedsScreen = () => {
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => { setActiveTab('materials'); setMaterialFilter('negative_balance'); setSearch(''); setCurrentPage(1); }}
|
||||
onClick={() => openQueue('materials', 'negative_balance')}
|
||||
className={`rounded-2xl border p-5 text-left shadow-sm transition-colors ${activeTab === 'materials' && materialFilter === 'negative_balance' ? 'border-red-400/45 bg-red-400/[0.07]' : 'border-dark-border bg-dark-card hover:border-red-400/35'}`}
|
||||
>
|
||||
<p className="text-xs font-bold uppercase tracking-widest text-dark-muted">Saldos a conferir</p>
|
||||
@@ -2348,7 +2368,7 @@ const PurchaseNeedsScreen = () => {
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => { setActiveTab('mapping'); setSearch(''); setCurrentPage(1); }}
|
||||
onClick={() => openQueue('mapping')}
|
||||
className={`rounded-2xl border p-5 text-left shadow-sm transition-colors ${activeTab === 'mapping' ? 'border-sky-400/45 bg-sky-400/[0.07]' : 'border-dark-border bg-dark-card hover:border-sky-400/35'}`}
|
||||
>
|
||||
<p className="text-xs font-bold uppercase tracking-widest text-dark-muted">Dados bloqueando o plano</p>
|
||||
@@ -2393,7 +2413,7 @@ const PurchaseNeedsScreen = () => {
|
||||
{ id: 'services' as const, label: `Serviços (${serviceNeeds.length})` },
|
||||
{ id: 'mapping' as const, label: `Mapear dados (${mappingNeeds.length})` },
|
||||
].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'}`}>
|
||||
<button key={tab.id} type="button" onClick={() => openQueue(tab.id)} 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}
|
||||
</button>
|
||||
))}
|
||||
@@ -2417,7 +2437,7 @@ const PurchaseNeedsScreen = () => {
|
||||
<button
|
||||
key={filter.id}
|
||||
type="button"
|
||||
onClick={() => { setMaterialFilter(filter.id); setCurrentPage(1); }}
|
||||
onClick={() => openQueue('materials', filter.id)}
|
||||
className={`rounded-lg border px-3 py-2 text-left text-xs font-bold transition-colors ${materialFilter === filter.id ? 'border-brand-primary/40 bg-brand-primary/10 text-brand-primary' : 'border-dark-border bg-dark-input text-dark-muted hover:border-brand-primary/35 hover:text-dark-text'}`}
|
||||
title={filter.description}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user