mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 14:55:23 +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:
@@ -340,25 +340,14 @@ const App: React.FC = () => {
|
|||||||
return rankMap;
|
return rankMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (filters.customer.length > 0) {
|
const euData = data2025.filter(r => !r.customer.toLowerCase().includes('uk'));
|
||||||
// Filtered mode: Rank products based on currently selected countries
|
const ukData = data2025.filter(r => r.customer.toLowerCase().includes('uk'));
|
||||||
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 {
|
return {
|
||||||
type: 'dual' as const,
|
eu: calculateTop50(euData),
|
||||||
eu: calculateTop50(euData),
|
uk: calculateTop50(ukData)
|
||||||
uk: calculateTop50(ukData)
|
};
|
||||||
};
|
}, [rawData]);
|
||||||
}
|
|
||||||
}, [rawData, filters.customer]);
|
|
||||||
|
|
||||||
// Combine Sales & Ads Data dynamically based on current filters
|
// Combine Sales & Ads Data dynamically based on current filters
|
||||||
const combinedAdsData = useMemo(() => {
|
const combinedAdsData = useMemo(() => {
|
||||||
|
|||||||
+67
-44
@@ -6,10 +6,8 @@ import { pivotWeeklySalesData, WeeklyPivotRow } from '../services/dataProcessor'
|
|||||||
interface WeeklyGridProps {
|
interface WeeklyGridProps {
|
||||||
data: CombinedKPIs[];
|
data: CombinedKPIs[];
|
||||||
top50Ranking?: {
|
top50Ranking?: {
|
||||||
type: 'filtered' | 'dual';
|
eu: Map<string, number>;
|
||||||
overall?: Map<string, number>;
|
uk: Map<string, number>;
|
||||||
eu?: Map<string, number>;
|
|
||||||
uk?: Map<string, number>;
|
|
||||||
};
|
};
|
||||||
onDrillDown?: (sku: string) => void;
|
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 [growthFilterMode, setGrowthFilterMode] = useState<'all' | 'up' | 'down' | 'stable'>('all');
|
||||||
const [growthThreshold, setGrowthThreshold] = useState(10);
|
const [growthThreshold, setGrowthThreshold] = useState(10);
|
||||||
const [showOnlyTop50, setShowOnlyTop50] = useState(false);
|
const [showOnlyTop50, setShowOnlyTop50] = useState(false);
|
||||||
|
const [top50Mode, setTop50Mode] = useState<'eu' | 'uk'>('eu');
|
||||||
|
|
||||||
// Default sort: most recent week, descending, units
|
// Default sort: most recent week, descending, units
|
||||||
const [sortConfig, setSortConfig] = useState<SortConfig>(() => {
|
const [sortConfig, setSortConfig] = useState<SortConfig>(() => {
|
||||||
@@ -78,7 +77,19 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data, top50Ranking, onDrillDown
|
|||||||
// Reset pagination when search changes
|
// Reset pagination when search changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentPage(1);
|
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') => {
|
const handleSort = useCallback((weekKey: string, metric: 'units' | 'spend' | 'rank' | 'gv') => {
|
||||||
setSortConfig(prev => {
|
setSortConfig(prev => {
|
||||||
@@ -115,10 +126,12 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data, top50Ranking, onDrillDown
|
|||||||
if (showOnlyTop50 && top50Ranking) {
|
if (showOnlyTop50 && top50Ranking) {
|
||||||
result = result.filter(r => {
|
result = result.filter(r => {
|
||||||
const asin = r.asin.trim().toUpperCase();
|
const asin = r.asin.trim().toUpperCase();
|
||||||
if (top50Ranking.type === 'filtered') {
|
const isUK = r.customer.toLowerCase().includes('uk');
|
||||||
return top50Ranking.overall?.has(asin);
|
|
||||||
|
if (top50Mode === 'eu') {
|
||||||
|
return !isUK && top50Ranking.eu.has(asin);
|
||||||
} else {
|
} 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 rankA = 999;
|
||||||
let rankB = 999;
|
let rankB = 999;
|
||||||
|
|
||||||
if (top50Ranking?.type === 'filtered') {
|
if (top50Ranking) {
|
||||||
rankA = top50Ranking.overall?.get(asinA) || 999;
|
if (top50Mode === 'eu') {
|
||||||
rankB = top50Ranking.overall?.get(asinB) || 999;
|
rankA = top50Ranking.eu.get(asinA) || 999;
|
||||||
} else if (top50Ranking?.type === 'dual') {
|
rankB = top50Ranking.eu.get(asinB) || 999;
|
||||||
// In dual mode, prioritize EU rank, then UK rank
|
} else {
|
||||||
rankA = top50Ranking.eu?.get(asinA) || top50Ranking.uk?.get(asinA) || 999;
|
rankA = top50Ranking.uk.get(asinA) || 999;
|
||||||
rankB = top50Ranking.eu?.get(asinB) || top50Ranking.uk?.get(asinB) || 999;
|
rankB = top50Ranking.uk.get(asinB) || 999;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return direction === 'asc' ? rankA - rankB : rankB - rankA;
|
return direction === 'asc' ? rankA - rankB : rankB - rankA;
|
||||||
@@ -290,31 +304,42 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data, top50Ranking, onDrillDown
|
|||||||
|
|
||||||
{/* Top 50 Filter Toggle */}
|
{/* Top 50 Filter Toggle */}
|
||||||
{top50Ranking && (
|
{top50Ranking && (
|
||||||
(top50Ranking.overall?.size || 0) > 0 ||
|
(top50Ranking.eu.size || 0) > 0 ||
|
||||||
(top50Ranking.eu?.size || 0) > 0 ||
|
(top50Ranking.uk.size || 0) > 0
|
||||||
(top50Ranking.uk?.size || 0) > 0
|
|
||||||
) && (
|
) && (
|
||||||
<button
|
<div className="flex bg-slate-950/50 p-1 rounded-xl border border-white/10 shadow-sm">
|
||||||
onClick={() => {
|
<button
|
||||||
const newMode = !showOnlyTop50;
|
onClick={() => {
|
||||||
setShowOnlyTop50(newMode);
|
const newMode = !showOnlyTop50;
|
||||||
if (newMode) {
|
setShowOnlyTop50(newMode);
|
||||||
handleSort('rank', 'rank');
|
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
|
||||||
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 shadow-lg shadow-amber-500/20'
|
||||||
? 'bg-gradient-to-r from-amber-500 to-orange-500 text-white border-amber-400 shadow-lg shadow-amber-500/20'
|
: 'text-slate-400 hover:text-amber-400'
|
||||||
: 'bg-slate-950/50 text-slate-400 border-white/10 hover:border-amber-500/50 hover:text-amber-400'
|
}`}
|
||||||
}`}
|
>
|
||||||
>
|
<span className="text-sm">🏆</span>
|
||||||
<span className="text-sm">🏆</span>
|
Top 50
|
||||||
Top 50 Only
|
</button>
|
||||||
|
|
||||||
{showOnlyTop50 && (
|
{showOnlyTop50 && (
|
||||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<div className="flex ml-1 p-0.5 bg-slate-900 rounded-lg border border-white/5">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
|
<button
|
||||||
</svg>
|
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 */}
|
{/* Export Button */}
|
||||||
@@ -447,14 +472,12 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data, top50Ranking, onDrillDown
|
|||||||
const ranks = [];
|
const ranks = [];
|
||||||
|
|
||||||
if (top50Ranking) {
|
if (top50Ranking) {
|
||||||
if (top50Ranking.type === 'filtered') {
|
if (top50Mode === 'eu') {
|
||||||
const rank = top50Ranking.overall?.get(asin);
|
const rank = top50Ranking.eu.get(asin);
|
||||||
if (rank) ranks.push({ rank, label: '', theme: 'amber' as const });
|
if (rank) ranks.push({ rank, label: 'EU', theme: 'indigo' as const });
|
||||||
} else {
|
} else {
|
||||||
const euRank = top50Ranking.eu?.get(asin);
|
const rank = top50Ranking.uk.get(asin);
|
||||||
const ukRank = top50Ranking.uk?.get(asin);
|
if (rank) ranks.push({ rank, label: 'UK', theme: 'blue' as const });
|
||||||
if (euRank) ranks.push({ rank: euRank, label: 'EU', theme: 'indigo' as const });
|
|
||||||
if (ukRank) ranks.push({ rank: ukRank, label: 'UK', theme: 'blue' as const });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user