Implement Excel-style filters (SKU, ASIN, Title, Line) across GRID, WEEKLY SALES, and FC26 tabs

This commit is contained in:
Christian Vidal Wolf
2026-02-09 12:00:39 +01:00
parent 827135a517
commit ac791b5265
6 changed files with 574 additions and 28 deletions
+60 -12
View File
@@ -2,13 +2,14 @@ import React, { useState, useMemo, useEffect, useRef } from 'react';
import {
LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer
} from 'recharts';
import { SalesRecord, PivotRow, AdsRecord, CombinedKPIs } from '../types';
import { pivotSalesData, generateXLSX, aggregateForTimeSeries, aggregateForComparisonTimeSeries, applyPanEUGrouping } from '../services/dataProcessor';
import { SalesRecord, PivotRow, AdsRecord, CombinedKPIs, ColumnFilterCondition } from '../types';
import { pivotSalesData, generateXLSX, aggregateForTimeSeries, aggregateForComparisonTimeSeries, applyPanEUGrouping, filterData } from '../services/dataProcessor';
import { DownloadIcon, FunnelIcon, CloseIcon, ChartIcon, TrendingIcon } from './Icons';
import { StockBadge } from './StockBadge';
import { Top50Badge } from './Top50Badge';
import { VendorStockBadge } from './VendorStockBadge';
import { BuyBoxWarningBadge } from './BuyBoxWarningBadge';
import { ExcelFilter } from './ExcelFilter';
interface DataGridProps {
data: SalesRecord[] | CombinedKPIs[];
@@ -297,6 +298,19 @@ const DataGrid: React.FC<DataGridProps> = ({ data, hasCustomerFilter, adsData, s
const [newFilterOperator, setNewFilterOperator] = useState<'gt' | 'lt'>('gt');
const [newFilterValue, setNewFilterValue] = useState<string>('');
// State for Column Filters
const [columnFilters, setColumnFilters] = useState<Record<string, ColumnFilterCondition>>({});
const handleColumnFilterChange = (columnKey: string, condition: ColumnFilterCondition | undefined) => {
setColumnFilters(prev => {
const next = { ...prev };
if (condition) next[columnKey] = condition;
else delete next[columnKey];
return next;
});
setCurrentPage(1);
};
// Effective dimensions for rendering
const effectiveDimensions = useMemo(() =>
selectedDimensions.length > 0 ? selectedDimensions : ['customer'],
@@ -307,10 +321,28 @@ const DataGrid: React.FC<DataGridProps> = ({ data, hasCustomerFilter, adsData, s
// Apply Pan-EU grouping when no customer filter is applied
const processedData = applyPanEUGrouping(data as SalesRecord[], hasCustomerFilter);
// Apply our comprehensive filters (includes Column Filters now)
const filterState: any = {
customer: [],
year: [],
month: [],
line: [],
asin: [],
sku: [],
title: [],
week: [],
stock: [],
vendorStock: [],
woc: [],
bulkSearch: searchTerm,
columnFilters
};
const filteredFlatData = filterData(processedData, filterState, stockMap, vendorStockMap, top50Mode);
// pivotSalesData now handles ads aggregation correctly because it receives CombinedKPIs
const { rows } = pivotSalesData(processedData, effectiveDimensions);
const { rows } = pivotSalesData(filteredFlatData, effectiveDimensions);
return rows;
}, [data, effectiveDimensions, hasCustomerFilter]);
}, [data, effectiveDimensions, hasCustomerFilter, columnFilters, searchTerm, stockMap, vendorStockMap, top50Mode]);
const { years } = useMemo(() => {
// We still need unique years for columns
@@ -452,13 +484,9 @@ const DataGrid: React.FC<DataGridProps> = ({ data, hasCustomerFilter, adsData, s
// 1. Filter
if (rowFilters.length > 0) {
const latestYear = years[0];
const prevYear = years[1];
result = result.filter(row => {
return rowFilters.every(filter => {
let rowValue = 0;
if (filter.metric.startsWith('total_sellOut_')) {
const y = filter.metric.split('_')[2];
rowValue = row.totalsByYear[y]?.sellOut || 0;
@@ -468,6 +496,8 @@ const DataGrid: React.FC<DataGridProps> = ({ data, hasCustomerFilter, adsData, s
rowValue = row.totalsByYear[y]?.units || 0;
}
else if (filter.metric === 'growth_sellOut') {
const latestYear = years[0];
const prevYear = years[1];
if (!prevYear) return true;
const curr = row.totalsByYear[latestYear]?.sellOut || 0;
const prev = row.totalsByYear[prevYear]?.sellOut || 0;
@@ -475,6 +505,8 @@ const DataGrid: React.FC<DataGridProps> = ({ data, hasCustomerFilter, adsData, s
rowValue = ((curr - prev) / prev) * 100;
}
else if (filter.metric === 'growth_units') {
const latestYear = years[0];
const prevYear = years[1];
if (!prevYear) return true;
const curr = row.totalsByYear[latestYear]?.units || 0;
const prev = row.totalsByYear[prevYear]?.units || 0;
@@ -1186,14 +1218,30 @@ const DataGrid: React.FC<DataGridProps> = ({ data, hasCustomerFilter, adsData, s
{/* Dynamic Dimension Headers */}
{effectiveDimensions.map(dim => {
const label = DIMENSION_OPTIONS.find(d => d.value === dim)?.label || dim;
// Extract unique values for this dimension from ALL pivot rows (pre-filter)
const uniqueValues = Array.from(new Set(pivotRows.map(r => String((r as any)[dim] || ''))))
.filter(Boolean)
.sort();
return (
<th
key={dim}
className={`px-4 py-3 border-b border-border cursor-pointer hover:text-white group bg-slate-950 ${dim === 'title' ? 'min-w-[450px]' : 'min-w-[150px]'}`}
onClick={() => requestSort(dim)}
className={`px-4 py-3 border-b border-border group bg-slate-950 ${dim === 'title' ? 'min-w-[450px]' : 'min-w-[150px]'}`}
>
<div className="flex items-center">
{label} {getSortIcon(dim)}
<div className="flex items-center justify-between">
<div
className="flex items-center cursor-pointer hover:text-white"
onClick={() => requestSort(dim)}
>
{label} {getSortIcon(dim)}
</div>
<ExcelFilter
columnKey={dim}
title={label}
uniqueValues={uniqueValues}
currentFilter={columnFilters[dim]}
onFilterChange={handleColumnFilterChange}
/>
</div>
</th>
);