From 6d0d8e6cf007935c7239eafc59515e2fcd2f6eb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Mon, 3 Aug 2026 11:09:00 -0300 Subject: [PATCH] Simplify product composition details --- src/pages/ProductDetails.tsx | 80 ++++-------------------------------- 1 file changed, 8 insertions(+), 72 deletions(-) diff --git a/src/pages/ProductDetails.tsx b/src/pages/ProductDetails.tsx index a87927e..c6e58bb 100644 --- a/src/pages/ProductDetails.tsx +++ b/src/pages/ProductDetails.tsx @@ -7,8 +7,8 @@ import DateRangePicker from '../components/DateRangePicker'; import SkuPlanningModal from '../components/SkuPlanningModal'; import ProductTypeBadge from '../components/ProductTypeBadge'; import RefreshStatus from '../components/RefreshStatus'; -import type { CutProductOverride, CuttingSettings, DateRange, ProductComposition, ProductDetailsAnalytics, StockData } from '../types'; -import { fetchCuttingSettings, fetchProductComposition, fetchProductDetailsAnalytics, fetchStock, saveCuttingSettings } from '../dataService'; +import type { CutProductOverride, CuttingSettings, DateRange, ProductComposition, ProductDetailsAnalytics } from '../types'; +import { fetchCuttingSettings, fetchProductComposition, fetchProductDetailsAnalytics, saveCuttingSettings } from '../dataService'; import { parseProductName } from '../productParsing'; import { formatColorLabel } from '../displayFormatters'; import { averageRecentValues, formatDateBucketLabel, formatDateBucketLongLabel, getAutoDateBucket, getMovingAverageWindow, getRangeDayCount, getDateBucketKey, type DateBucket } from '../chartUtils'; @@ -132,7 +132,6 @@ const ProductDetails = () => { }>(); const [details, setDetails] = useState(null); const [composition, setComposition] = useState(null); - const [materialStock, setMaterialStock] = useState([]); const [isCompositionLoading, setIsCompositionLoading] = useState(true); const [isLoading, setIsLoading] = useState(true); const [chartMetric, setChartMetric] = useState('quantity'); @@ -172,16 +171,14 @@ const ProductDetails = () => { setIsLoading(true); setIsCompositionLoading(true); - const [productDetails, productComposition, stock] = await Promise.all([ + const [productDetails, productComposition] = await Promise.all([ fetchProductDetailsAnalytics(id, dateRange), - fetchProductComposition(id), - fetchStock() + fetchProductComposition(id) ]); if (isMounted) { setDetails(productDetails); setComposition(productComposition); - setMaterialStock(stock); setIsLoading(false); setIsCompositionLoading(false); } @@ -334,41 +331,6 @@ const ProductDetails = () => { : formatDateBucketLongLabel(selectedProductPoint.date, dateBucket) : ''; const maxVariantQuantity = Math.max(...variantBreakdown.map(variant => variant.quantitySold), 0); - const normalizeMaterialKey = (value: string) => value - .normalize('NFD') - .replace(/[\u0300-\u036f]/g, '') - .replace(/\s+/g, ' ') - .trim() - .toUpperCase(); - const isServiceComponent = (name: string) => /\b(?:SERVI[CÇ]O|FRETE|TINTURARIA|TECELAGEM|COSTURA|ESTAMPARIA|LAVANDERIA)\b/i.test(name); - const isSuspiciousConsumption = (quantity: number, unit: string) => ( - ['kg', 'quilo', 'quilos'].includes(unit.trim().toLowerCase()) && quantity >= 0.5 - ); - const materialPlan = (composition?.components || []).map(component => { - const isService = isServiceComponent(component.componentName); - const candidates = new Set([ - component.componentTinyId, - component.componentSku, - component.productId || '', - component.componentName - ].map(normalizeMaterialKey).filter(Boolean)); - const stockMatch = materialStock.find(item => ( - candidates.has(normalizeMaterialKey(item.produto_id)) || candidates.has(normalizeMaterialKey(item.nome)) - )); - const availableStock = isService ? null : stockMatch ? getPlanningStock(stockMatch.saldo) : null; - const requiredForPeriod = totalSold * component.quantityPerUnit; - const productionCapacity = availableStock === null || component.quantityPerUnit <= 0 - ? null - : Math.floor(availableStock / component.quantityPerUnit); - const periodCoverage = requiredForPeriod > 0 && availableStock !== null - ? (availableStock / requiredForPeriod) * periodDays - : null; - return { component, availableStock, requiredForPeriod, productionCapacity, periodCoverage, isService, suspicious: isSuspiciousConsumption(component.quantityPerUnit, component.unit) }; - }); - const blockingMaterial = materialPlan - .filter(item => !item.isService && item.productionCapacity !== null) - .sort((a, b) => (a.productionCapacity || 0) - (b.productionCapacity || 0))[0]; - return (
@@ -451,24 +413,15 @@ const ProductDetails = () => {
-
+

Composição

{composition && (

- Necessidade para as {formatNumber(totalSold)} un. vendidas no período e saldo atual do Tiny. + Para produzir 1 UN de {composition.finishedProductSku || productInfo.id}

)}
- {blockingMaterial && ( -
- Limitante: {blockingMaterial.component.componentName} · {formatNumber(blockingMaterial.productionCapacity || 0)} un. possíveis -
- )}
{isCompositionLoading ? (
Carregando composição…
@@ -482,21 +435,17 @@ const ProductDetails = () => {
) : (
- +
- - - - - {materialPlan.map(({ component, requiredForPeriod, availableStock, productionCapacity, periodCoverage, isService, suspicious }) => ( + {composition.components.map(component => ( - - - - ))}
Produto / insumo SKU Quantidade por unidade UnidadeValidaçãoNecessário no períodoEstoque TinyCobertura
{component.productId ? ( @@ -508,19 +457,6 @@ const ProductDetails = () => { {component.componentSku || '—'} {formatNumber(component.quantityPerUnit)} {component.unit || '—'} - {isService ? Serviço - : suspicious ? Revisar consumo - : OK} - {formatNumber(requiredForPeriod)} {component.unit || ''}{isService ? 'Não aplicável' : availableStock === null ? 'Sem vínculo' : `${formatNumber(availableStock)} ${component.unit || ''}`} - {isService ? 'Terceirizar' - : availableStock === null - ? 'Revisar link' - : `${formatNumber(productionCapacity || 0)} un. · ${periodCoverage === null ? '-' : periodCoverage > 365 ? '> 365 dias' : `${formatNumber(periodCoverage)} dias`}`} -