feat: enable sorting and filtering by Revenue in Weekly Sales grid

This commit is contained in:
Christian Vidal Wolf
2026-02-19 16:48:48 +01:00
parent ab79f2ca6e
commit 557703c5ea
2 changed files with 19 additions and 16 deletions
+4 -4
View File
@@ -2,7 +2,7 @@ import React, { useState, useRef, useEffect } from 'react';
export interface NumericFilterConfig { export interface NumericFilterConfig {
week: string; week: string;
metric: 'units' | 'spend' | 'gv'; metric: 'units' | 'spend' | 'revenue' | 'gv';
operator: '=' | '>' | '>=' | '<' | '<=' | 'between'; operator: '=' | '>' | '>=' | '<' | '<=' | 'between';
value: number; value: number;
value2?: number; value2?: number;
@@ -10,7 +10,7 @@ export interface NumericFilterConfig {
interface NumericColumnFilterProps { interface NumericColumnFilterProps {
week: string; week: string;
metric: 'units' | 'spend' | 'gv'; metric: 'units' | 'spend' | 'revenue' | 'gv';
currentFilter: NumericFilterConfig | null; currentFilter: NumericFilterConfig | null;
onFilterChange: (filter: NumericFilterConfig | null) => void; onFilterChange: (filter: NumericFilterConfig | null) => void;
accentColor?: 'indigo' | 'amber' | 'teal'; accentColor?: 'indigo' | 'amber' | 'teal';
@@ -127,8 +127,8 @@ export const NumericColumnFilter: React.FC<NumericColumnFilterProps> = ({
setIsOpen(!isOpen); setIsOpen(!isOpen);
}} }}
className={`flex items-center justify-center w-4 h-4 rounded text-[9px] font-black transition-all ${isActive className={`flex items-center justify-center w-4 h-4 rounded text-[9px] font-black transition-all ${isActive
? colors.active ? colors.active
: `text-slate-600 ${colors.hover}` : `text-slate-600 ${colors.hover}`
}`} }`}
title={isActive ? `${metric}: ${operatorSymbol} ${currentFilter?.value}${currentFilter?.value2 ? ` - ${currentFilter.value2}` : ''}` : `Filter ${metric}`} title={isActive ? `${metric}: ${operatorSymbol} ${currentFilter?.value}${currentFilter?.value2 ? ` - ${currentFilter.value2}` : ''}` : `Filter ${metric}`}
> >
+15 -12
View File
@@ -35,7 +35,7 @@ interface WeeklyGridProps {
type SortConfig = { type SortConfig = {
key: string; // weekKey or 'rank' key: string; // weekKey or 'rank'
direction: 'asc' | 'desc'; direction: 'asc' | 'desc';
metric: 'units' | 'spend' | 'rank' | 'gv'; metric: 'units' | 'spend' | 'revenue' | 'rank' | 'gv';
} | null; } | null;
const ROWS_PER_PAGE = 50; const ROWS_PER_PAGE = 50;
@@ -370,11 +370,11 @@ const WeeklyGrid: React.FC<WeeklyGridProps & { top50Mode: 'eu' | 'uk' }> = ({
const [numericFilters, setNumericFilters] = useState<NumericFilterConfig[]>([]); const [numericFilters, setNumericFilters] = useState<NumericFilterConfig[]>([]);
// Helper to get/set filter for a specific week+metric // Helper to get/set filter for a specific week+metric
const getNumericFilter = useCallback((week: string, metric: 'units' | 'spend' | 'gv') => { const getNumericFilter = useCallback((week: string, metric: 'units' | 'spend' | 'revenue' | 'gv') => {
return numericFilters.find(f => f.week === week && f.metric === metric) || null; return numericFilters.find(f => f.week === week && f.metric === metric) || null;
}, [numericFilters]); }, [numericFilters]);
const setNumericFilter = useCallback((filter: NumericFilterConfig | null, week: string, metric: 'units' | 'spend' | 'gv') => { const setNumericFilter = useCallback((filter: NumericFilterConfig | null, week: string, metric: 'units' | 'spend' | 'revenue' | 'gv') => {
setNumericFilters(prev => { setNumericFilters(prev => {
// Remove existing filter for this week+metric // Remove existing filter for this week+metric
const filtered = prev.filter(f => !(f.week === week && f.metric === metric)); const filtered = prev.filter(f => !(f.week === week && f.metric === metric));
@@ -400,7 +400,7 @@ const WeeklyGrid: React.FC<WeeklyGridProps & { top50Mode: 'eu' | 'uk' }> = ({
setDisplayCount(50); setDisplayCount(50);
}, [debouncedSearch, showOnlyTop50, top50Mode]); }, [debouncedSearch, showOnlyTop50, top50Mode]);
const handleSort = useCallback((weekKey: string, metric: 'units' | 'spend' | 'rank' | 'gv') => { const handleSort = useCallback((weekKey: string, metric: 'units' | 'spend' | 'revenue' | 'rank' | 'gv') => {
setSortConfig(prev => { setSortConfig(prev => {
if (prev?.key === weekKey && prev.metric === metric) { if (prev?.key === weekKey && prev.metric === metric) {
return { key: weekKey, direction: prev.direction === 'asc' ? 'desc' : 'asc', metric }; return { key: weekKey, direction: prev.direction === 'asc' ? 'desc' : 'asc', metric };
@@ -559,6 +559,8 @@ const WeeklyGrid: React.FC<WeeklyGridProps & { top50Mode: 'eu' | 'uk' }> = ({
value = row.unitsByWeek[filter.week] || 0; value = row.unitsByWeek[filter.week] || 0;
} else if (filter.metric === 'spend') { } else if (filter.metric === 'spend') {
value = row.spendByWeek[filter.week] || 0; value = row.spendByWeek[filter.week] || 0;
} else if (filter.metric === 'revenue') {
value = row.revenueByWeek?.[filter.week] || 0;
} else if (filter.metric === 'gv') { } else if (filter.metric === 'gv') {
value = row.gvByWeek?.[filter.week] || 0; value = row.gvByWeek?.[filter.week] || 0;
} }
@@ -590,7 +592,8 @@ const WeeklyGrid: React.FC<WeeklyGridProps & { top50Mode: 'eu' | 'uk' }> = ({
}); });
} else { } else {
const metricKey = metric === 'units' ? 'unitsByWeek' : const metricKey = metric === 'units' ? 'unitsByWeek' :
metric === 'spend' ? 'spendByWeek' : 'gvByWeek'; metric === 'spend' ? 'spendByWeek' :
metric === 'revenue' ? 'revenueByWeek' : 'gvByWeek';
result.sort((a, b) => { result.sort((a, b) => {
const valA = a[metricKey][weekKey] || 0; const valA = a[metricKey][weekKey] || 0;
@@ -889,22 +892,22 @@ const WeeklyGrid: React.FC<WeeklyGridProps & { top50Mode: 'eu' | 'uk' }> = ({
</div> </div>
<div <div
className={`flex-1 p-1.5 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'}`} className={`flex-1 p-1.5 transition-colors flex items-center justify-center gap-1 border-b border-white/5 ${sortConfig?.key === week && sortConfig.metric === (primaryMetric === 'units' ? 'units' : 'revenue') ? 'bg-indigo-500/5 text-indigo-400' : 'text-slate-500'}`}
> >
<span <span
onClick={() => handleSort(week, 'units')} onClick={() => handleSort(week, primaryMetric === 'units' ? 'units' : 'revenue')}
className="text-[9px] cursor-pointer hover:text-indigo-300" className="text-[9px] cursor-pointer hover:text-indigo-300"
> >
Units {primaryMetric === 'units' ? 'Units' : 'Revenue'}
</span> </span>
{sortConfig?.key === week && sortConfig.metric === 'units' && ( {sortConfig?.key === week && sortConfig.metric === (primaryMetric === 'units' ? 'units' : 'revenue') && (
<span className="text-xs font-bold leading-none">{sortConfig.direction === 'asc' ? '↑' : '↓'}</span> <span className="text-xs font-bold leading-none">{sortConfig.direction === 'asc' ? '↑' : '↓'}</span>
)} )}
<NumericColumnFilter <NumericColumnFilter
week={week} week={week}
metric="units" metric={primaryMetric === 'units' ? 'units' : 'revenue'}
currentFilter={getNumericFilter(week, 'units')} currentFilter={getNumericFilter(week, primaryMetric === 'units' ? 'units' : 'revenue')}
onFilterChange={(f) => setNumericFilter(f, week, 'units')} onFilterChange={(f) => setNumericFilter(f, week, primaryMetric === 'units' ? 'units' : 'revenue')}
accentColor="indigo" accentColor="indigo"
/> />
</div> </div>