Enhance Excel-style filters with numeric operators and integrate into GRID metrics (Sell Out, Units)

This commit is contained in:
Christian Vidal Wolf
2026-02-09 12:09:01 +01:00
parent d5bcbeda1a
commit 81c946b3e1
3 changed files with 75 additions and 14 deletions
+70 -13
View File
@@ -473,7 +473,7 @@ const DataGrid: React.FC<DataGridProps> = ({ data, filters, hasCustomerFilter, a
}
}
// 1. Filter
// 1. Filter (Legacy Row Filters)
if (rowFilters.length > 0) {
result = result.filter(row => {
return rowFilters.every(filter => {
@@ -512,6 +512,45 @@ const DataGrid: React.FC<DataGridProps> = ({ data, filters, hasCustomerFilter, a
});
}
// 1b. Filter (New Excel Column Filters for Metrics)
(Object.entries(columnFilters) as [string, ColumnFilterCondition][]).forEach(([key, condition]) => {
if (!condition) return;
if (!key.startsWith('total_')) return; // Metric filters in this view start with 'total_'
const isSellOut = key.includes('_sellOut_');
const year = key.split('_')[2];
if (condition.selectedValues && condition.selectedValues.length > 0) {
result = result.filter(row => {
const val = isSellOut ? row.totalsByYear[year]?.sellOut : row.totalsByYear[year]?.units;
return condition.selectedValues?.includes(String(val || 0));
});
}
if (condition.textFilter) {
const { operator, value } = condition.textFilter;
const filterNum = parseFloat(value);
if (isNaN(filterNum)) return;
result = result.filter(row => {
const rowVal = (isSellOut ? row.totalsByYear[year]?.sellOut : row.totalsByYear[year]?.units) || 0;
switch (operator) {
case 'equals': return rowVal === filterNum;
case 'notEquals': return rowVal !== filterNum;
case 'gt': return rowVal > filterNum;
case 'lt': return rowVal < filterNum;
case 'gte': return rowVal >= filterNum;
case 'lte': return rowVal <= filterNum;
// Text-like operators on numeric values (convert to string)
case 'contains': return String(rowVal).includes(value);
case 'startsWith': return String(rowVal).startsWith(value);
case 'endsWith': return String(rowVal).endsWith(value);
default: return true;
}
});
}
});
// 2. Sort
if (sortConfig.key) {
// Create a copy to avoid mutating the original array and ensure React detects the change
@@ -1244,20 +1283,38 @@ const DataGrid: React.FC<DataGridProps> = ({ data, filters, hasCustomerFilter, a
return (
<React.Fragment key={year}>
{/* Sell Out & Units */}
<th
className="px-4 py-3 border-b border-border text-right cursor-pointer hover:text-white bg-slate-950 min-w-[100px]"
onClick={() => requestSort(`total_sellOut_${year}`)}
>
<div className="flex items-center justify-end">
Sell Out {year} {getSortIcon(`total_sellOut_${year}`)}
<th className="px-4 py-3 border-b border-border text-right bg-slate-950 min-w-[120px]">
<div className="flex items-center justify-end gap-2">
<div
className="flex items-center cursor-pointer hover:text-white"
onClick={() => requestSort(`total_sellOut_${year}`)}
>
Sell Out {year} {getSortIcon(`total_sellOut_${year}`)}
</div>
<ExcelFilter
columnKey={`total_sellOut_${year}`}
title={`Sell Out ${year}`}
uniqueValues={Array.from(new Set(pivotRows.map(r => String(r.totalsByYear[year]?.sellOut || 0)))).sort((a, b) => parseFloat(a as string) - parseFloat(b as string))}
currentFilter={columnFilters[`total_sellOut_${year}`]}
onFilterChange={handleColumnFilterChange}
/>
</div>
</th>
<th
className="px-4 py-3 border-b border-border text-right cursor-pointer hover:text-white bg-slate-950 min-w-[80px]"
onClick={() => requestSort(`total_units_${year}`)}
>
<div className="flex items-center justify-end">
Units {year} {getSortIcon(`total_units_${year}`)}
<th className="px-4 py-3 border-b border-border text-right bg-slate-950 min-w-[100px]">
<div className="flex items-center justify-end gap-2">
<div
className="flex items-center cursor-pointer hover:text-white"
onClick={() => requestSort(`total_units_${year}`)}
>
Units {year} {getSortIcon(`total_units_${year}`)}
</div>
<ExcelFilter
columnKey={`total_units_${year}`}
title={`Units ${year}`}
uniqueValues={Array.from(new Set(pivotRows.map(r => String(r.totalsByYear[year]?.units || 0)))).sort((a, b) => parseFloat(a as string) - parseFloat(b as string))}
currentFilter={columnFilters[`total_units_${year}`]}
onFilterChange={handleColumnFilterChange}
/>
</div>
</th>
+4
View File
@@ -21,6 +21,10 @@ const OPERATORS = [
{ label: 'No comienza por', value: 'notStartsWith' },
{ label: 'Termina con', value: 'endsWith' },
{ label: 'No termina con', value: 'notEndsWith' },
{ label: 'Mayor que', value: 'gt' },
{ label: 'Menor que', value: 'lt' },
{ label: 'Mayor o igual que', value: 'gte' },
{ label: 'Menor o igual que', value: 'lte' },
] as const;
export const ExcelFilter: React.FC<ExcelFilterProps> = ({
+1 -1
View File
@@ -32,7 +32,7 @@ export interface FilterState {
export interface ColumnFilterCondition {
textFilter?: {
operator: 'equals' | 'notEquals' | 'contains' | 'notContains' | 'startsWith' | 'notStartsWith' | 'endsWith' | 'notEndsWith';
operator: 'equals' | 'notEquals' | 'contains' | 'notContains' | 'startsWith' | 'notStartsWith' | 'endsWith' | 'notEndsWith' | 'gt' | 'lt' | 'gte' | 'lte';
value: string;
};
selectedValues?: string[];