mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 13:15:24 +02:00
feat: separate Pan-EU and UK Top 50 results in Weekly Sales
- App.tsx: Always calculate both EU and UK Top 50 maps. - WeeklyGrid.tsx: Added top50Mode state and EU/UK toggle. - WeeklyGrid.tsx: Updated filtering and sorting to respect the selected Top 50 mode. - WeeklyGrid.tsx: Enhanced product badges to show selected group rank.
This commit is contained in:
+67
-44
@@ -6,10 +6,8 @@ import { pivotWeeklySalesData, WeeklyPivotRow } from '../services/dataProcessor'
|
||||
interface WeeklyGridProps {
|
||||
data: CombinedKPIs[];
|
||||
top50Ranking?: {
|
||||
type: 'filtered' | 'dual';
|
||||
overall?: Map<string, number>;
|
||||
eu?: Map<string, number>;
|
||||
uk?: Map<string, number>;
|
||||
eu: Map<string, number>;
|
||||
uk: Map<string, number>;
|
||||
};
|
||||
onDrillDown?: (sku: string) => void;
|
||||
}
|
||||
@@ -66,6 +64,7 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data, top50Ranking, onDrillDown
|
||||
const [growthFilterMode, setGrowthFilterMode] = useState<'all' | 'up' | 'down' | 'stable'>('all');
|
||||
const [growthThreshold, setGrowthThreshold] = useState(10);
|
||||
const [showOnlyTop50, setShowOnlyTop50] = useState(false);
|
||||
const [top50Mode, setTop50Mode] = useState<'eu' | 'uk'>('eu');
|
||||
|
||||
// Default sort: most recent week, descending, units
|
||||
const [sortConfig, setSortConfig] = useState<SortConfig>(() => {
|
||||
@@ -78,7 +77,19 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data, top50Ranking, onDrillDown
|
||||
// Reset pagination when search changes
|
||||
useEffect(() => {
|
||||
setCurrentPage(1);
|
||||
}, [debouncedSearch, showOnlyTop50]);
|
||||
}, [debouncedSearch, showOnlyTop50, top50Mode]);
|
||||
|
||||
// Auto-detect best Top 50 mode based on currently selected data
|
||||
useEffect(() => {
|
||||
const hasUK = rows.some(r => r.customer.toLowerCase().includes('uk'));
|
||||
const hasEU = rows.some(r => !r.customer.toLowerCase().includes('uk'));
|
||||
|
||||
if (hasUK && !hasEU) {
|
||||
setTop50Mode('uk');
|
||||
} else if (hasEU && !hasUK) {
|
||||
setTop50Mode('eu');
|
||||
}
|
||||
}, [rows]);
|
||||
|
||||
const handleSort = useCallback((weekKey: string, metric: 'units' | 'spend' | 'rank' | 'gv') => {
|
||||
setSortConfig(prev => {
|
||||
@@ -115,10 +126,12 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data, top50Ranking, onDrillDown
|
||||
if (showOnlyTop50 && top50Ranking) {
|
||||
result = result.filter(r => {
|
||||
const asin = r.asin.trim().toUpperCase();
|
||||
if (top50Ranking.type === 'filtered') {
|
||||
return top50Ranking.overall?.has(asin);
|
||||
const isUK = r.customer.toLowerCase().includes('uk');
|
||||
|
||||
if (top50Mode === 'eu') {
|
||||
return !isUK && top50Ranking.eu.has(asin);
|
||||
} else {
|
||||
return top50Ranking.eu?.has(asin) || top50Ranking.uk?.has(asin);
|
||||
return isUK && top50Ranking.uk.has(asin);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -181,13 +194,14 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data, top50Ranking, onDrillDown
|
||||
let rankA = 999;
|
||||
let rankB = 999;
|
||||
|
||||
if (top50Ranking?.type === 'filtered') {
|
||||
rankA = top50Ranking.overall?.get(asinA) || 999;
|
||||
rankB = top50Ranking.overall?.get(asinB) || 999;
|
||||
} else if (top50Ranking?.type === 'dual') {
|
||||
// In dual mode, prioritize EU rank, then UK rank
|
||||
rankA = top50Ranking.eu?.get(asinA) || top50Ranking.uk?.get(asinA) || 999;
|
||||
rankB = top50Ranking.eu?.get(asinB) || top50Ranking.uk?.get(asinB) || 999;
|
||||
if (top50Ranking) {
|
||||
if (top50Mode === 'eu') {
|
||||
rankA = top50Ranking.eu.get(asinA) || 999;
|
||||
rankB = top50Ranking.eu.get(asinB) || 999;
|
||||
} else {
|
||||
rankA = top50Ranking.uk.get(asinA) || 999;
|
||||
rankB = top50Ranking.uk.get(asinB) || 999;
|
||||
}
|
||||
}
|
||||
|
||||
return direction === 'asc' ? rankA - rankB : rankB - rankA;
|
||||
@@ -290,31 +304,42 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data, top50Ranking, onDrillDown
|
||||
|
||||
{/* Top 50 Filter Toggle */}
|
||||
{top50Ranking && (
|
||||
(top50Ranking.overall?.size || 0) > 0 ||
|
||||
(top50Ranking.eu?.size || 0) > 0 ||
|
||||
(top50Ranking.uk?.size || 0) > 0
|
||||
(top50Ranking.eu.size || 0) > 0 ||
|
||||
(top50Ranking.uk.size || 0) > 0
|
||||
) && (
|
||||
<button
|
||||
onClick={() => {
|
||||
const newMode = !showOnlyTop50;
|
||||
setShowOnlyTop50(newMode);
|
||||
if (newMode) {
|
||||
handleSort('rank', 'rank');
|
||||
}
|
||||
}}
|
||||
className={`flex items-center gap-2 px-3 py-1.5 rounded-lg text-xs font-bold transition-all border ${showOnlyTop50
|
||||
? 'bg-gradient-to-r from-amber-500 to-orange-500 text-white border-amber-400 shadow-lg shadow-amber-500/20'
|
||||
: 'bg-slate-950/50 text-slate-400 border-white/10 hover:border-amber-500/50 hover:text-amber-400'
|
||||
}`}
|
||||
>
|
||||
<span className="text-sm">🏆</span>
|
||||
Top 50 Only
|
||||
<div className="flex bg-slate-950/50 p-1 rounded-xl border border-white/10 shadow-sm">
|
||||
<button
|
||||
onClick={() => {
|
||||
const newMode = !showOnlyTop50;
|
||||
setShowOnlyTop50(newMode);
|
||||
if (newMode) handleSort('rank', 'rank');
|
||||
}}
|
||||
className={`flex items-center gap-2 px-3 py-1.5 rounded-lg text-xs font-bold transition-all ${showOnlyTop50
|
||||
? 'bg-gradient-to-r from-amber-500 to-orange-500 text-white shadow-lg shadow-amber-500/20'
|
||||
: 'text-slate-400 hover:text-amber-400'
|
||||
}`}
|
||||
>
|
||||
<span className="text-sm">🏆</span>
|
||||
Top 50
|
||||
</button>
|
||||
|
||||
{showOnlyTop50 && (
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
<div className="flex ml-1 p-0.5 bg-slate-900 rounded-lg border border-white/5">
|
||||
<button
|
||||
onClick={() => setTop50Mode('eu')}
|
||||
className={`px-2 py-1 rounded-md text-[10px] font-black uppercase tracking-widest transition-all ${top50Mode === 'eu' ? 'bg-indigo-500 text-white shadow-sm' : 'text-slate-500 hover:text-slate-300'}`}
|
||||
>
|
||||
EU
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setTop50Mode('uk')}
|
||||
className={`px-2 py-1 rounded-md text-[10px] font-black uppercase tracking-widest transition-all ${top50Mode === 'uk' ? 'bg-blue-500 text-white shadow-sm' : 'text-slate-500 hover:text-slate-300'}`}
|
||||
>
|
||||
UK
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Export Button */}
|
||||
@@ -447,14 +472,12 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data, top50Ranking, onDrillDown
|
||||
const ranks = [];
|
||||
|
||||
if (top50Ranking) {
|
||||
if (top50Ranking.type === 'filtered') {
|
||||
const rank = top50Ranking.overall?.get(asin);
|
||||
if (rank) ranks.push({ rank, label: '', theme: 'amber' as const });
|
||||
if (top50Mode === 'eu') {
|
||||
const rank = top50Ranking.eu.get(asin);
|
||||
if (rank) ranks.push({ rank, label: 'EU', theme: 'indigo' as const });
|
||||
} else {
|
||||
const euRank = top50Ranking.eu?.get(asin);
|
||||
const ukRank = top50Ranking.uk?.get(asin);
|
||||
if (euRank) ranks.push({ rank: euRank, label: 'EU', theme: 'indigo' as const });
|
||||
if (ukRank) ranks.push({ rank: ukRank, label: 'UK', theme: 'blue' as const });
|
||||
const rank = top50Ranking.uk.get(asin);
|
||||
if (rank) ranks.push({ rank, label: 'UK', theme: 'blue' as const });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user