diff --git a/components/mkt/MktDataView.tsx b/components/mkt/MktDataView.tsx index b482808..a76e9c5 100644 --- a/components/mkt/MktDataView.tsx +++ b/components/mkt/MktDataView.tsx @@ -168,63 +168,73 @@ export default function MktDataView({ rawData, adsData }: MktDataViewProps) { unitsMap.set(asin, (unitsMap.get(asin) || 0) + r.units); }); - // ASIN → global ad spend (all filtered data, no hardcoded year) - // Include ALL records (even empty ASIN) so total matches ADs Weekly tab + // ASIN → global ad spend const adsSpendMap = new Map(); + let totalAdsPassed = 0; adsData.forEach(r => { const asin = r.asin.trim().toUpperCase(); adsSpendMap.set(asin, (adsSpendMap.get(asin) || 0) + r.cost); + totalAdsPassed += r.cost; }); - // Deals/Promos/Chargebacks are 2025-only data — only apply when 2025 is in scope + // Deals/Promos/Chargebacks const has2025 = rawData.some(r => r.year === 2025) || adsData.some(r => r.year === 2025); const getDeals = (asin: string) => has2025 ? (dealsMap.get(asin) || 0) : 0; const getPromos = (asin: string) => has2025 ? (promosMap.get(asin) || 0) : 0; const getChargebacks = (asin: string) => has2025 ? (chargebacksMap.get(asin) || 0) : 0; - // Build products from sales ASINs - const products = Array.from(metaMap.entries()) - .map(([asin, meta]): Product => ({ - id: asin, - asin, - sku: meta.sku, - name: meta.title || asin, - image: '', - category: meta.line, - brand: '', - grossSales: sellOutMap.get(asin) || 0, - unitsSold: unitsMap.get(asin) || 0, - ppcSpend: adsSpendMap.get(asin) || 0, - deals: getDeals(asin), - promos: getPromos(asin), - chargebacks: getChargebacks(asin), - chargebacksPrevMonth: 0, - cogs: 0, - })); - - // Add ASINs (or unassigned spend) that have ad spend but no sales records - adsSpendMap.forEach((spend, asin) => { - if (!metaMap.has(asin) && spend > 0) { - products.push({ - id: asin || '__unassigned__', - asin: asin || '—', - sku: '', - name: asin ? asin : 'Unassigned Ad Spend', + // 1. Start with all sales-based ASINs + const products: Product[] = Array.from(metaMap.entries()) + .map(([asin, meta]): Product => { + const spend = adsSpendMap.get(asin) || 0; + adsSpendMap.delete(asin); // Mark as processed + return { + id: asin, + asin, + sku: meta.sku, + name: meta.title || asin, image: '', - category: 'Other', + category: meta.line, brand: '', - grossSales: 0, - unitsSold: 0, + grossSales: sellOutMap.get(asin) || 0, + unitsSold: unitsMap.get(asin) || 0, ppcSpend: spend, - deals: asin ? getDeals(asin) : 0, - promos: asin ? getPromos(asin) : 0, - chargebacks: asin ? getChargebacks(asin) : 0, + deals: getDeals(asin), + promos: getPromos(asin), + chargebacks: getChargebacks(asin), chargebacksPrevMonth: 0, cogs: 0, - }); - } + }; + }); + + // 2. Add remaining ad spend (including "" ASIN and ASINs without sales) + adsSpendMap.forEach((spend, asin) => { + if (spend === 0) return; + products.push({ + id: asin || '__unassigned__', + asin: asin || '—', + sku: '', + name: asin ? asin : 'Unassigned Ad Spend (incl. Sponsored Brands)', + image: '', + category: 'Other', + brand: '', + grossSales: 0, + unitsSold: 0, + ppcSpend: spend, + deals: asin ? getDeals(asin) : 0, + promos: asin ? getPromos(asin) : 0, + chargebacks: asin ? getChargebacks(asin) : 0, + chargebacksPrevMonth: 0, + cogs: 0, + }); }); + // Debug check + const finalTotal = products.reduce((sum, p) => sum + p.ppcSpend, 0); + if (Math.abs(finalTotal - totalAdsPassed) > 0.01) { + console.warn(`[MktDataView] PPC Spend Mismatch: Total Ads=${totalAdsPassed.toFixed(2)}, Table Sum=${finalTotal.toFixed(2)}`); + } + return products; }, [rawData, adsData, dealsMap, promosMap, chargebacksMap]);