feat: implement country-aware Top 50 ranking

- App.tsx: Update top50Ranking2025 to calculate ranks based on active country filters
- App.tsx: Add split Pan-EU vs UK ranking when no filters are active
- WeeklyGrid.tsx: Add support for multiple ranking badges (EU/UK)
- WeeklyGrid.tsx: Enhance Top50Badge with labels and color themes
- WeeklyGrid.tsx: Update sorting and filtering logic for new ranking structure
This commit is contained in:
Christian Vidal Wolf
2026-01-22 13:01:04 +01:00
parent 8005a99a48
commit 0214509ffe
2 changed files with 102 additions and 34 deletions
+35 -18
View File
@@ -254,31 +254,48 @@ const App: React.FC = () => {
const filteredAdsData = useMemo(() => filterAdsData(adsData, filters), [adsData, filters]);
const aggregatedData = useMemo(() => aggregateData(filteredData), [filteredData]);
// Calculate static Top 50 Best Sellers for 2025 (independent of current filters)
// Calculate country-aware Top 50 Best Sellers for 2025
const top50Ranking2025 = useMemo(() => {
// Filter only 2025 data
const data2025 = rawData.filter(r => r.year === 2025);
// Aggregate Sell Out by ASIN
const asinTotals = new Map<string, number>();
data2025.forEach(r => {
const asin = r.asin.trim().toUpperCase();
asinTotals.set(asin, (asinTotals.get(asin) || 0) + r.sellOut);
});
const calculateTop50 = (entries: SalesRecord[]) => {
const asinTotals = new Map<string, number>();
entries.forEach(r => {
const asin = r.asin.trim().toUpperCase();
asinTotals.set(asin, (asinTotals.get(asin) || 0) + r.sellOut);
});
// Sort by total Sell Out descending and take top 50
const sorted = Array.from(asinTotals.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, 50);
const sorted = Array.from(asinTotals.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, 50);
// Create a Map of ASIN -> rank (1-50)
const rankMap = new Map<string, number>();
sorted.forEach(([asin], index) => {
rankMap.set(asin, index + 1);
});
const rankMap = new Map<string, number>();
sorted.forEach(([asin], index) => {
rankMap.set(asin, index + 1);
});
return rankMap;
};
return rankMap;
}, [rawData]);
if (filters.customer.length > 0) {
// Filtered mode: Rank products based on currently selected countries
const filtered2025 = data2025.filter(r => filters.customer.includes(r.customer));
return {
type: 'filtered' as const,
overall: calculateTop50(filtered2025)
};
} else {
// Dual mode: Separate Pan-EU and UK rankings when no countries are selected
const euData = data2025.filter(r => !r.customer.toLowerCase().includes('uk'));
const ukData = data2025.filter(r => r.customer.toLowerCase().includes('uk'));
return {
type: 'dual' as const,
eu: calculateTop50(euData),
uk: calculateTop50(ukData)
};
}
}, [rawData, filters.customer]);
// Combine Sales & Ads Data dynamically based on current filters
const combinedAdsData = useMemo(() => {