mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 16:35:23 +02:00
Refactor: Unified Top 50 Badge logic and shared component integration
This commit is contained in:
@@ -4,6 +4,7 @@ import { CombinedKPIs } from '../types';
|
||||
import { pivotWeeklySalesData, WeeklyPivotRow, PAN_EU_COUNTRIES } from '../services/dataProcessor';
|
||||
import { StockBadge } from './StockBadge';
|
||||
import { InColumnStockFilter } from './InColumnStockFilter';
|
||||
import { Top50Badge } from './Top50Badge';
|
||||
|
||||
interface WeeklyGridProps {
|
||||
data: CombinedKPIs[];
|
||||
@@ -37,26 +38,6 @@ const useDebounce = (value: string, delay: number) => {
|
||||
return debouncedValue;
|
||||
};
|
||||
|
||||
// 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 (
|
||||
<span
|
||||
className={`inline-flex items-center justify-center px-1.5 py-0.5 rounded text-[9px] font-black bg-gradient-to-r ${themeClasses[theme]} text-white shadow-sm border`}
|
||||
title={`Top ${rank} Best Seller 2025 ${label ? `(${label})` : ''}`}
|
||||
>
|
||||
<span className="mr-0.5">🏆</span>
|
||||
{label && <span className="mr-0.5 opacity-90">{label}</span>}
|
||||
{rank}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const WeeklyRow: React.FC<{
|
||||
row: WeeklyPivotRow;
|
||||
weeks: string[];
|
||||
@@ -71,22 +52,13 @@ const WeeklyRow: React.FC<{
|
||||
const ranks: { rank: number; label: string; theme: 'amber' | 'blue' | 'indigo' }[] = [];
|
||||
const asin = row.asin.trim().toUpperCase();
|
||||
|
||||
const isUKSelected = customerFilters.some(c => c.toLowerCase().includes('uk'));
|
||||
const isEUPartialSelected = customerFilters.some(c => PAN_EU_COUNTRIES.includes(c));
|
||||
const hasNoCustomerFilter = customerFilters.length === 0;
|
||||
|
||||
if (top50Ranking) {
|
||||
// Only show UK rank if UK is explicitly selected
|
||||
if (top50Mode === 'uk' && isUKSelected) {
|
||||
const rank = top50Ranking.uk.get(asin);
|
||||
if (rank) ranks.push({ rank, label: 'UK', theme: 'blue' });
|
||||
}
|
||||
// Only show EU rank if at least one Pan-EU country is selected
|
||||
// OR if no filters are selected (User might want to see them all then, but user said "only when... is selected")
|
||||
// Actually the prompt says: "only appear when in the filters... is selected"
|
||||
else if (top50Mode === 'eu' && isEUPartialSelected) {
|
||||
if (top50Mode === 'eu') {
|
||||
const rank = top50Ranking.eu.get(asin);
|
||||
if (rank) ranks.push({ rank, label: 'EU', theme: 'indigo' });
|
||||
} else {
|
||||
const rank = top50Ranking.uk.get(asin);
|
||||
if (rank) ranks.push({ rank, label: 'UK', theme: 'blue' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +129,7 @@ const WeeklyRow: React.FC<{
|
||||
);
|
||||
});
|
||||
|
||||
const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data, top50Ranking, onDrillDown, stockMap, stockFilter, onStockFilterChange, customerFilters }) => {
|
||||
const WeeklyGrid: React.FC<WeeklyGridProps & { top50Mode: 'eu' | 'uk' }> = ({ data, top50Ranking, onDrillDown, stockMap, stockFilter, onStockFilterChange, customerFilters, top50Mode }) => {
|
||||
// Pivot data - memoized
|
||||
const { rows, weeks: allWeeks } = useMemo(() => pivotWeeklySalesData(data), [data]);
|
||||
|
||||
@@ -170,7 +142,6 @@ 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');
|
||||
const [displayCount, setDisplayCount] = useState(50);
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -185,20 +156,9 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data, top50Ranking, onDrillDown
|
||||
// Reset pagination when search changes
|
||||
useEffect(() => {
|
||||
setCurrentPage(1);
|
||||
setDisplayCount(50);
|
||||
}, [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 => {
|
||||
if (prev?.key === weekKey && prev.metric === metric) {
|
||||
@@ -287,7 +247,7 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data, top50Ranking, onDrillDown
|
||||
}
|
||||
|
||||
return result;
|
||||
}, [rows, debouncedSearch, showOnlyTop50, top50Ranking, growthFilterMode, growthThreshold, sortConfig, weeks]);
|
||||
}, [rows, debouncedSearch, showOnlyTop50, top50Ranking, growthFilterMode, growthThreshold, sortConfig, weeks, top50Mode]);
|
||||
|
||||
// 2. Sort results
|
||||
const sortedRows = useMemo(() => {
|
||||
@@ -359,7 +319,6 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data, top50Ranking, onDrillDown
|
||||
return rowData;
|
||||
});
|
||||
|
||||
// Add Totals row
|
||||
const totalsRow: any = {
|
||||
SKU: 'TOTALS',
|
||||
ASIN: '',
|
||||
@@ -443,25 +402,8 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data, top50Ranking, onDrillDown
|
||||
}`}
|
||||
>
|
||||
<span className="text-sm">🏆</span>
|
||||
Top 50
|
||||
Top 50 ({top50Mode.toUpperCase()})
|
||||
</button>
|
||||
|
||||
{showOnlyTop50 && (
|
||||
<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>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -531,12 +473,10 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data, top50Ranking, onDrillDown
|
||||
className={`p-0 text-[10px] font-black uppercase tracking-widest text-center border-r border-white/10 min-w-[130px] transition-colors select-none ${sortConfig?.key === week ? 'bg-white/[0.02]' : ''}`}
|
||||
>
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Week Label */}
|
||||
<div className="p-2 border-b border-white/5 bg-slate-800/30 text-xs text-white">
|
||||
{week.split('-')[1]}/{week.split('-')[0].slice(-2)}
|
||||
</div>
|
||||
|
||||
{/* Units Sort Trigger */}
|
||||
<div
|
||||
onClick={() => handleSort(week, 'units')}
|
||||
className={`flex-1 p-1.5 cursor-pointer hover:bg-indigo-500/10 transition-colors flex items-center justify-center gap-1 border-b border-white/5 ${sortConfig?.key === week && sortConfig.metric === 'units' ? 'bg-indigo-500/5 text-indigo-400' : 'text-slate-500 hover:text-slate-300'}`}
|
||||
@@ -547,7 +487,6 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data, top50Ranking, onDrillDown
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Spend Sort Trigger */}
|
||||
<div
|
||||
onClick={() => handleSort(week, 'spend')}
|
||||
className={`flex-1 p-1.5 cursor-pointer hover:bg-amber-500/10 transition-colors flex items-center justify-center gap-1 border-b border-white/5 ${sortConfig?.key === week && sortConfig.metric === 'spend' ? 'bg-amber-500/5 text-amber-400' : 'text-slate-500 hover:text-slate-300'}`}
|
||||
@@ -558,7 +497,6 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data, top50Ranking, onDrillDown
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* GV Sort Trigger */}
|
||||
<div
|
||||
onClick={() => handleSort(week, 'gv')}
|
||||
className={`flex-1 p-1.5 cursor-pointer hover:bg-teal-500/10 transition-colors flex items-center justify-center gap-1 ${sortConfig?.key === week && sortConfig.metric === 'gv' ? 'bg-teal-500/5 text-teal-400' : 'text-slate-500 hover:text-slate-300'}`}
|
||||
|
||||
Reference in New Issue
Block a user