From 40737faaa529ce25d1db459c83726f2c76a255bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Mon, 10 Aug 2026 09:58:58 -0300 Subject: [PATCH] Unify purchase need controls --- src/pages/Supplies.tsx | 215 +++++++++++++++++------------------------ 1 file changed, 86 insertions(+), 129 deletions(-) diff --git a/src/pages/Supplies.tsx b/src/pages/Supplies.tsx index ac9963a..dc31323 100644 --- a/src/pages/Supplies.tsx +++ b/src/pages/Supplies.tsx @@ -2084,18 +2084,6 @@ const formatNeedQuantity = (need: SupplyPurchaseNeed, value: number) => ( `${formatNumber(value)} ${getNeedUnit(need)}` ); -const summarizePurchaseNeeds = (needs: SupplyPurchaseNeed[]) => { - const totalsByUnit = needs.reduce>((totals, need) => { - if (need.purchaseKg <= 0) return totals; - const unit = getNeedUnit(need); - totals[unit] = (totals[unit] || 0) + need.purchaseKg; - return totals; - }, {}); - - const summaries = Object.entries(totalsByUnit).map(([unit, total]) => `${formatNumber(total)} ${unit}`); - return summaries.length ? summaries.join(' + ') : '0 kg'; -}; - const getNeedDisplayName = (need: SupplyPurchaseNeed) => ( need.material.replace(/^Cadastrar (consumo|rendimento):\s*/i, '') ); @@ -2104,6 +2092,8 @@ const hasNegativeMaterialBalance = (need: SupplyPurchaseNeed) => need.stockKg < type PurchaseNeedTab = 'materials' | 'services'; type MaterialPurchaseFilter = 'to_buy' | 'negative_balance' | 'no_stock' | 'all'; +type ServicePurchaseFilter = 'to_schedule' | 'covered' | 'all'; +type PurchaseNeedFilter = MaterialPurchaseFilter | ServicePurchaseFilter; const sumNeedProducts = ( need: SupplyPurchaseNeed, @@ -2296,6 +2286,7 @@ const PurchaseNeedsScreen = () => { const requestedFilter = searchParams.get('filter'); const activeTab: PurchaseNeedTab = requestedTab === 'services' ? 'services' : 'materials'; const materialFilter: MaterialPurchaseFilter = requestedFilter === 'negative_balance' || requestedFilter === 'no_stock' || requestedFilter === 'all' ? requestedFilter : 'to_buy'; + const serviceFilter: ServicePurchaseFilter = requestedFilter === 'covered' || requestedFilter === 'all' ? requestedFilter : 'to_schedule'; const [currentPage, setCurrentPage] = useState(1); const [itemsPerPage, setItemsPerPage] = useState(10); @@ -2324,10 +2315,11 @@ const PurchaseNeedsScreen = () => { }; }, []); - const openQueue = (tab: PurchaseNeedTab, filter: MaterialPurchaseFilter = 'to_buy') => { + const openQueue = (tab: PurchaseNeedTab, filter?: PurchaseNeedFilter) => { + const nextFilter = filter || (tab === 'materials' ? 'to_buy' : 'to_schedule'); setSearch(''); setCurrentPage(1); - setSearchParams(tab === 'materials' ? { tab, filter } : { tab }); + setSearchParams({ tab, filter: nextFilter }); }; const materialNeeds = summary.purchaseNeeds.filter(need => !need.missingReference && !isServicePurchaseNeed(need)); @@ -2341,13 +2333,18 @@ const PurchaseNeedsScreen = () => { if (materialFilter === 'no_stock') return need.stockKg === 0 && need.purchaseKg > 0; return true; }); + const filteredServiceNeeds = serviceNeeds.filter(need => { + if (serviceFilter === 'to_schedule') return need.purchaseKg > 0; + if (serviceFilter === 'covered') return need.purchaseKg <= 0; + return true; + }); const activeNeeds = activeTab === 'materials' ? [...filteredMaterialNeeds].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; }) - : serviceNeeds; + : filteredServiceNeeds; 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) @@ -2357,130 +2354,90 @@ const PurchaseNeedsScreen = () => { const startIndex = (safeCurrentPage - 1) * itemsPerPage; const paginatedNeeds = visibleNeeds.slice(startIndex, startIndex + itemsPerPage); const purchaseItemCount = purchaseNeeds.length; - const suggestedPurchaseSummary = summarizePurchaseNeeds(purchaseNeeds); - const pageTitle = 'Necessidade de Compra'; - const pageSubtitle = 'Materiais e serviços terceirizados para execução; pendências de cadastro ficam em Dados para revisar.'; - const materialFilters: Array<{ id: MaterialPurchaseFilter; label: string; description: string; count: number }> = [ - { id: 'to_buy', label: 'Comprar agora', description: 'Materiais com saldo insuficiente para a demanda.', count: purchaseItemCount }, - { id: 'negative_balance', label: 'Saldo negativo', description: 'Divergências de estoque que precisam de conferência.', count: negativeBalanceNeeds.length }, - { id: 'no_stock', label: 'Sem saldo', description: 'Materiais sem saldo disponível no Tiny.', count: noStockNeeds.length }, - { id: 'all', label: 'Todos os materiais', description: 'Inclui itens cobertos para auditoria da conta.', count: materialNeeds.length }, + const materialFilters: Array<{ id: MaterialPurchaseFilter; label: string; count: number }> = [ + { id: 'to_buy', label: 'Comprar agora', count: purchaseItemCount }, + { id: 'negative_balance', label: 'Saldo negativo', count: negativeBalanceNeeds.length }, + { id: 'no_stock', label: 'Sem saldo', count: noStockNeeds.length }, + { id: 'all', label: 'Todos os materiais', count: materialNeeds.length }, ]; - const activeMaterialFilter = materialFilters.find(filter => filter.id === materialFilter); - const panelTitle = activeTab === 'materials' ? activeMaterialFilter?.label || 'Compra de materiais' : 'Serviços terceirizados'; - const panelSubtitle = activeTab === 'materials' - ? activeMaterialFilter?.description || 'Demanda de produto → material → saldo disponível → quantidade para comprar.' - : 'Necessidades de beneficiamento e serviços. Não entram no total de compra de materiais.'; + const serviceFilters: Array<{ id: ServicePurchaseFilter; label: string; count: number }> = [ + { id: 'to_schedule', label: 'A programar', count: serviceNeeds.filter(need => need.purchaseKg > 0).length }, + { id: 'covered', label: 'Sem programação', count: serviceNeeds.filter(need => need.purchaseKg <= 0).length }, + { id: 'all', label: 'Todos os serviços', count: serviceNeeds.length }, + ]; + const activeFilter = activeTab === 'materials' ? materialFilter : serviceFilter; return (
-
-
- -
-

Compra sugerida

-

{suggestedPurchaseSummary}

-

necessidade após saldo e recebimentos

+
+
+
+ +
-
-
-
-
-

{panelTitle}

-

{panelSubtitle}

-
-
- - -
+ ))}
-
-
- {[ - { id: 'materials' as const, label: `Compra (${purchaseItemCount})` }, - { id: 'services' as const, label: `Serviços (${serviceNeeds.length})` }, - ].map(tab => ( - - ))} -
- -
- {activeTab === 'materials' && ( -
- {materialFilters.map(filter => ( - - ))} -
- )} + + +
+
{isLoading ? ( -
+

Calculando necessidade...

) : visibleNeeds.length ? ( -
+ <>
- {activeTab === 'materials' ? ( - - ) : ( - - )} + {activeTab === 'materials' ? : }
{ }} className="border-t border-dark-border px-4 py-3" /> -
+ ) : ( -
+
-

{summary.purchaseNeeds.length ? 'Nenhuma necessidade encontrada' : 'Nenhuma necessidade de compra'}

-

{summary.purchaseNeeds.length ? 'Ajuste a busca.' : 'Cadastre planos de malha para gerar demanda e aprove recebimentos para abater estoque.'}

+

{activeTab === 'services' ? 'Nenhum serviço encontrado' : 'Nenhuma necessidade encontrada'}

+

{summary.purchaseNeeds.length ? 'Ajuste a busca ou o filtro.' : 'Cadastre planos de malha para gerar demanda e aprove recebimentos para abater estoque.'}

)}