From 9357aed662ef10df990ad268f5e5079aeafcece5 Mon Sep 17 00:00:00 2001 From: Christian Vidal Wolf Date: Thu, 22 Jan 2026 13:07:43 +0100 Subject: [PATCH] fix: ensure Weekly Sales filters update totals and isolate products - App.tsx: Add globalAsinMetadata to provide context for ad filtering - dataProcessor.ts: Update filterAdsData to respect product line filters via metadata - dataProcessor.ts: Update mergeSalesAndAdsData to use global metadata for ads-only records - Ensures consistent calculation of totals and visibility when filtering by Line or ASIN --- App.tsx | 19 +++++++++++--- services/dataProcessor.ts | 55 +++++++++++++++++++++++++-------------- 2 files changed, 52 insertions(+), 22 deletions(-) diff --git a/App.tsx b/App.tsx index c1cea30..ff157ab 100644 --- a/App.tsx +++ b/App.tsx @@ -250,8 +250,21 @@ const App: React.FC = () => { // Derive Data + const globalAsinMetadata = useMemo(() => { + const metaMap = new Map(); + rawData.forEach(r => { + const asin = r.asin.trim().toUpperCase(); + const existing = metaMap.get(asin); + // Keep most complete title + if (!existing || (r.title && r.title.length > (existing.title?.length || 0))) { + metaMap.set(asin, { sku: r.sku, title: r.title, line: r.line }); + } + }); + return metaMap; + }, [rawData]); + const filteredData = useMemo(() => filterData(rawData, filters), [rawData, filters]); - const filteredAdsData = useMemo(() => filterAdsData(adsData, filters), [adsData, filters]); + const filteredAdsData = useMemo(() => filterAdsData(adsData, filters, globalAsinMetadata), [adsData, filters, globalAsinMetadata]); const aggregatedData = useMemo(() => aggregateData(filteredData), [filteredData]); // Calculate country-aware Top 50 Best Sellers for 2025 @@ -299,8 +312,8 @@ const App: React.FC = () => { // Combine Sales & Ads Data dynamically based on current filters const combinedAdsData = useMemo(() => { - return mergeSalesAndAdsData(filteredData, filteredAdsData); - }, [filteredData, filteredAdsData]); + return mergeSalesAndAdsData(filteredData, filteredAdsData, globalAsinMetadata); + }, [filteredData, filteredAdsData, globalAsinMetadata]); // Derive Context Data (Product Line Context when drilling down) const contextAggregatedData = useMemo(() => { diff --git a/services/dataProcessor.ts b/services/dataProcessor.ts index 45c820d..bc6c5cf 100644 --- a/services/dataProcessor.ts +++ b/services/dataProcessor.ts @@ -441,15 +441,19 @@ export const processAdsExcel = async (fileOrBuffer: File | ArrayBuffer): Promise // --- DATA MERGING --- -export const mergeSalesAndAdsData = (salesData: SalesRecord[], adsData: AdsRecord[]): CombinedKPIs[] => { +export const mergeSalesAndAdsData = ( + salesData: SalesRecord[], + adsData: AdsRecord[], + asinMetadataMap?: Map +): CombinedKPIs[] => { // Key for both sales and ads: ASIN|Customer|Year|Week const createKey = (asin: string, customer: string, year: number, week: number) => `${asin.trim().toUpperCase()}|${customer.trim().toUpperCase()}|${year}|${week}`; - // Pre-build metadata lookup map for O(1) access (instead of O(n) nested loop) - const asinMetadata = new Map(); + // 1. Initialize metadata lookup map with provided global map if available, otherwise build from current sales + const asinMetadata = asinMetadataMap || new Map(); - // 1. Aggregate Sales by ASIN|Customer|Year|Week (combine all SKUs) + // 2. Aggregate Sales by ASIN|Customer|Year|Week (combine all SKUs) const salesMap = new Map best metadata - const metaKey = `${sale.asin.trim().toUpperCase()}|${sale.customer.trim().toUpperCase()}`; - const existingMeta = asinMetadata.get(metaKey); - if (!existingMeta || (sale.title && sale.title.length > (existingMeta.title?.length || 0))) { - asinMetadata.set(metaKey, { sku: sale.sku, title: sale.title, line: sale.line }); + // If no global map provided, build it on the fly + if (!asinMetadataMap) { + const metaKey = sale.asin.trim().toUpperCase(); + const existingMeta = asinMetadata.get(metaKey); + if (!existingMeta || (sale.title && sale.title.length > (existingMeta.title?.length || 0))) { + asinMetadata.set(metaKey, { sku: sale.sku, title: sale.title, line: sale.line }); + } } if (salesMap.has(key)) { @@ -502,7 +508,7 @@ export const mergeSalesAndAdsData = (salesData: SalesRecord[], adsData: AdsRecor } }); - // 2. Aggregate Ads by ASIN|Customer|Year|Week + // 3. Aggregate Ads by ASIN|Customer|Year|Week const adsMap = new Map(); adsData.forEach(ad => { const key = createKey(ad.asin, ad.country, ad.year, ad.week); @@ -522,7 +528,7 @@ export const mergeSalesAndAdsData = (salesData: SalesRecord[], adsData: AdsRecor const mergedData: CombinedKPIs[] = []; const processedKeys = new Set(); - // 3. Create ONE record per ASIN/Customer/Year/Week from sales + // 4. Create ONE record per ASIN/Customer/Year/Week from sales salesMap.forEach((sale, key) => { processedKeys.add(key); const ad = adsMap.get(key); @@ -552,7 +558,7 @@ export const mergeSalesAndAdsData = (salesData: SalesRecord[], adsData: AdsRecor month: sale.month, week: sale.week, year: sale.year, - asin: sale.asin, + asin: sale.asin.trim().toUpperCase(), title: sale.title, line: sale.line, sku: sale.sku, @@ -576,11 +582,11 @@ export const mergeSalesAndAdsData = (salesData: SalesRecord[], adsData: AdsRecor }); }); - // 4. Add ads-only records - use O(1) metadata lookup instead of O(n) loop + // 5. Add ads-only records - use provided metadata for line/title adsMap.forEach((ad, key) => { if (!processedKeys.has(key)) { - const metaKey = `${ad.asin.trim().toUpperCase()}|${ad.country.trim().toUpperCase()}`; - const meta = asinMetadata.get(metaKey); + const asin = ad.asin.trim().toUpperCase(); + const meta = asinMetadata.get(asin); mergedData.push({ id: `ads-only-${key}`, @@ -589,7 +595,7 @@ export const mergeSalesAndAdsData = (salesData: SalesRecord[], adsData: AdsRecor month: 'N/A', week: ad.week, year: ad.year, - asin: ad.asin, + asin: asin, title: meta?.title || ad.asin, line: meta?.line || 'Unassigned', sku: meta?.sku || '', @@ -621,8 +627,12 @@ export const mergeSalesAndAdsData = (salesData: SalesRecord[], adsData: AdsRecor // --- EXISTING HELPERS --- -// Filter Ads Data by Country, Year, Week, and ASIN -export const filterAdsData = (adsData: AdsRecord[], filters: FilterState): AdsRecord[] => { +// Filter Ads Data by Country, Year, Week, ASIN, and Product Line +export const filterAdsData = ( + adsData: AdsRecord[], + filters: FilterState, + asinMetadata?: Map +): AdsRecord[] => { return adsData.filter(ad => { // Country/Customer match (ads use 'country', sales use 'customer') const countryMatch = filters.customer.length === 0 @@ -641,7 +651,14 @@ export const filterAdsData = (adsData: AdsRecord[], filters: FilterState): AdsRe const asinMatch = filters.asin.length === 0 || filters.asin.some(a => a.toUpperCase() === ad.asin.toUpperCase()); - return countryMatch && yearMatch && weekMatch && asinMatch; + // Line match (Requires metadata) + let lineMatch = true; + if (filters.line.length > 0 && asinMetadata) { + const meta = asinMetadata.get(ad.asin.trim().toUpperCase()); + lineMatch = meta ? filters.line.includes(meta.line) : false; + } + + return countryMatch && yearMatch && weekMatch && asinMatch && lineMatch; }); };