From 1c8ec7b835d291c7c269b922ec53fbcbe6a851b1 Mon Sep 17 00:00:00 2001 From: Christian Vidal Wolf Date: Thu, 22 Jan 2026 12:02:21 +0100 Subject: [PATCH] feat: add Top 50 Best Sellers badge in Weekly Sales MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Calculate static Top 50 ranking based on 2025 Sell Out (independent of filters) - Display trophy badge with position number for Top 50 ASINs - Badge shows 🏆 followed by rank (1-50) - Ranking stays static regardless of date/country filters applied - Pre-computed ranking based on cumulative 2025 data --- App.tsx | 28 ++++++++++++- components/WeeklyGrid.tsx | 87 +++++++++++++++++++++++---------------- 2 files changed, 78 insertions(+), 37 deletions(-) diff --git a/App.tsx b/App.tsx index 17bf791..a8b6a85 100644 --- a/App.tsx +++ b/App.tsx @@ -254,6 +254,32 @@ 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) + const top50Ranking2025 = useMemo(() => { + // Filter only 2025 data + const data2025 = rawData.filter(r => r.year === 2025); + + // Aggregate Sell Out by ASIN + const asinTotals = new Map(); + data2025.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); + + // Create a Map of ASIN -> rank (1-50) + const rankMap = new Map(); + sorted.forEach(([asin], index) => { + rankMap.set(asin, index + 1); + }); + + return rankMap; + }, [rawData]); + // Combine Sales & Ads Data dynamically based on current filters const combinedAdsData = useMemo(() => { return mergeSalesAndAdsData(filteredData, filteredAdsData); @@ -427,7 +453,7 @@ const App: React.FC = () => { )} }> {view === 'table' && 0} adsData={filteredAdsData} />} - {view === 'weekly' && } + {view === 'weekly' && } {view === 'movers' && } diff --git a/components/WeeklyGrid.tsx b/components/WeeklyGrid.tsx index aede8d6..4e4a6d3 100644 --- a/components/WeeklyGrid.tsx +++ b/components/WeeklyGrid.tsx @@ -5,6 +5,7 @@ import { pivotWeeklySalesData, WeeklyPivotRow } from '../services/dataProcessor' interface WeeklyGridProps { data: CombinedKPIs[]; + top50Ranking?: Map; // Static ranking of Top 50 ASINs by 2025 Sell Out } type SortConfig = { @@ -26,7 +27,17 @@ const useDebounce = (value: string, delay: number) => { return debouncedValue; }; -const WeeklyGrid: React.FC = ({ data }) => { +// Top 50 Badge Component +const Top50Badge: React.FC<{ rank: number }> = ({ rank }) => ( + + 🏆 {rank} + +); + +const WeeklyGrid: React.FC = ({ data, top50Ranking }) => { // Pivot data - memoized const { rows, weeks: allWeeks } = useMemo(() => pivotWeeklySalesData(data), [data]); @@ -327,42 +338,46 @@ const WeeklyGrid: React.FC = ({ data }) => { {paginatedRows.length > 0 ? ( - paginatedRows.map((row) => ( - - -
-
- {row.sku || '-'} - {row.asin} -
- {row.title} - {row.line} -
- - {weeks.map((week, idx) => { - const val = row.unitsByWeek[week] || 0; - const prevVal = row.unitsByWeek[weeks[idx + 1]] || 0; - const spend = row.spendByWeek[week] || 0; - return ( - -
-
- 0 ? (sortConfig?.key === week && sortConfig.metric === 'units' ? 'text-indigo-400' : 'text-white') : 'text-slate-700'}`}> - {val > 0 ? val.toLocaleString('de-DE') : '-'} - - {val > 0 && renderGrowth(val, prevVal)} -
- {spend > 0 && ( - - €{spend.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 })} - - )} + paginatedRows.map((row) => { + const top50Rank = top50Ranking?.get(row.asin.trim().toUpperCase()); + return ( + + +
+
+ {top50Rank && } + {row.sku || '-'} + {row.asin}
- - ); - })} - - )) + {row.title} + {row.line} +
+ + {weeks.map((week, idx) => { + const val = row.unitsByWeek[week] || 0; + const prevVal = row.unitsByWeek[weeks[idx + 1]] || 0; + const spend = row.spendByWeek[week] || 0; + return ( + +
+
+ 0 ? (sortConfig?.key === week && sortConfig.metric === 'units' ? 'text-indigo-400' : 'text-white') : 'text-slate-700'}`}> + {val > 0 ? val.toLocaleString('de-DE') : '-'} + + {val > 0 && renderGrowth(val, prevVal)} +
+ {spend > 0 && ( + + €{spend.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 })} + + )} +
+ + ); + })} + + ); + }) ) : (