mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 12:45:23 +02:00
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
This commit is contained in:
@@ -250,8 +250,21 @@ const App: React.FC = () => {
|
||||
|
||||
|
||||
// Derive Data
|
||||
const globalAsinMetadata = useMemo(() => {
|
||||
const metaMap = new Map<string, { sku: string; title: string; line: string }>();
|
||||
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(() => {
|
||||
|
||||
+36
-19
@@ -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<string, { sku: string; title: string; line: string }>
|
||||
): 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<string, { sku: string; title: string; line: string }>();
|
||||
// 1. Initialize metadata lookup map with provided global map if available, otherwise build from current sales
|
||||
const asinMetadata = asinMetadataMap || new Map<string, { sku: string; title: string; line: string }>();
|
||||
|
||||
// 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<string, {
|
||||
sellOut: number;
|
||||
units: number;
|
||||
@@ -469,11 +473,13 @@ export const mergeSalesAndAdsData = (salesData: SalesRecord[], adsData: AdsRecor
|
||||
|
||||
const key = createKey(sale.asin, sale.customer, sale.year, weekNum);
|
||||
|
||||
// Build metadata map: ASIN|Customer -> 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<string, AdsRecord>();
|
||||
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<string>();
|
||||
|
||||
// 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<string, { line: string }>
|
||||
): 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;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user