From 3de971801dec0b3e4cf1b0a6e6466fdf7a9344e6 Mon Sep 17 00:00:00 2001 From: Christian Vidal Wolf Date: Mon, 26 Jan 2026 10:51:30 +0100 Subject: [PATCH] feat: add Top 50 filter button and badges to Ads Performance tab --- App.tsx | 2 +- components/AdsPerformance.tsx | 149 +++++++++++++++++++++++++++------- 2 files changed, 122 insertions(+), 29 deletions(-) diff --git a/App.tsx b/App.tsx index 88056bc..bbe9711 100644 --- a/App.tsx +++ b/App.tsx @@ -531,7 +531,7 @@ const App: React.FC = () => { {view === 'table' && 0} adsData={filteredAdsData} />} {view === 'weekly' && } {view === 'movers' && } - {view === 'ads' && } + {view === 'ads' && } diff --git a/components/AdsPerformance.tsx b/components/AdsPerformance.tsx index 9f90397..82c0fee 100644 --- a/components/AdsPerformance.tsx +++ b/components/AdsPerformance.tsx @@ -4,12 +4,38 @@ import { CombinedKPIs, FilterState } from '../types'; interface AdsPerformanceProps { data: CombinedKPIs[]; filters: FilterState; + top50Ranking?: { + eu: Map; + uk: Map; + }; } type SortKey = keyof CombinedKPIs | 'acos' | 'roas' | 'tacos' | 'ctr' | 'cpc' | 'cvrUnits'; -const AdsPerformance: React.FC = ({ data, filters }) => { +// Top 50 Badge Component +const Top50Badge: React.FC<{ rank: number; label?: string; theme?: 'amber' | 'blue' | 'indigo' }> = ({ rank, label, theme = 'amber' }) => { + const themeClasses = { + amber: 'from-amber-500 to-orange-500 border-amber-400/50', + blue: 'from-blue-500 to-cyan-500 border-blue-400/50', + indigo: 'from-indigo-500 to-purple-500 border-indigo-400/50', + }; + + return ( + + 🏆 + {label && {label}} + {rank} + + ); +}; + +const AdsPerformance: React.FC = ({ data, filters, top50Ranking }) => { const [searchTerm, setSearchTerm] = useState(''); + const [showOnlyTop50, setShowOnlyTop50] = useState(false); + const [top50Mode, setTop50Mode] = useState<'eu' | 'uk'>('eu'); const [sortConfig, setSortConfig] = useState<{ key: SortKey; direction: 'asc' | 'desc' }>({ key: 'cost', direction: 'desc' @@ -137,7 +163,22 @@ const AdsPerformance: React.FC = ({ data, filters }) => { // 3. Filter and Sort const filteredAndSorted = useMemo(() => { - let result = aggregatedByAsin.filter(item => + let result = aggregatedByAsin; + + // Top 50 Filter + if (showOnlyTop50 && top50Ranking) { + result = result.filter(item => { + const asin = item.asin.trim().toUpperCase(); + if (top50Mode === 'eu') { + return top50Ranking.eu.has(asin); + } else { + return top50Ranking.uk.has(asin); + } + }); + } + + // Search Filter + result = result.filter(item => item.asin.toLowerCase().includes(searchTerm.toLowerCase()) || item.sku.toLowerCase().includes(searchTerm.toLowerCase()) || item.title.toLowerCase().includes(searchTerm.toLowerCase()) @@ -150,7 +191,7 @@ const AdsPerformance: React.FC = ({ data, filters }) => { }); return result; - }, [aggregatedByAsin, searchTerm, sortConfig]); + }, [aggregatedByAsin, searchTerm, sortConfig, showOnlyTop50, top50Ranking, top50Mode]); const handleSort = (key: SortKey) => { setSortConfig(prev => ({ @@ -213,6 +254,41 @@ const AdsPerformance: React.FC = ({ data, filters }) => {

+ {/* Top 50 Filter Toggle */} + {top50Ranking && ( + (top50Ranking.eu.size || 0) > 0 || + (top50Ranking.uk.size || 0) > 0 + ) && ( +
+ + + {showOnlyTop50 && ( +
+ + +
+ )} +
+ )} = ({ data, filters }) => { - {filteredAndSorted.map((row: any) => ( - - -
- {row.asin} - SKU: {row.sku} - {row.title} -
- - - - - - - - - - - - - - - - - ))} + {filteredAndSorted.map((row: any) => { + const asin = row.asin.trim().toUpperCase(); + const ranks: { rank: number; label: string; theme: 'amber' | 'blue' | 'indigo' }[] = []; + + if (top50Ranking) { + const euRank = top50Ranking.eu.get(asin); + const ukRank = top50Ranking.uk.get(asin); + if (euRank) ranks.push({ rank: euRank, label: 'EU', theme: 'indigo' }); + if (ukRank) ranks.push({ rank: ukRank, label: 'UK', theme: 'blue' }); + } + + return ( + + +
+
+ {ranks.map((r, i) => ( + + ))} + {row.asin} +
+ SKU: {row.sku} + {row.title} +
+ + + + + + + + + + + + + + + + + ); + })}