mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 12:25:22 +02:00
Implement Excel-style filters (SKU, ASIN, Title, Line) across GRID, WEEKLY SALES, and FC26 tabs
This commit is contained in:
+118
-3
@@ -13,6 +13,8 @@ import {
|
||||
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
|
||||
LineChart, Line, Legend, ComposedChart, Area
|
||||
} from 'recharts';
|
||||
import { ExcelFilter } from './ExcelFilter';
|
||||
import { ColumnFilterCondition } from '../types';
|
||||
|
||||
interface ForecastViewProps {
|
||||
data: ProductForecastData[];
|
||||
@@ -195,6 +197,18 @@ const ForecastView: React.FC<ForecastViewProps> = ({
|
||||
const [displayCount, setDisplayCount] = useState(50);
|
||||
const [sortConfig, setSortConfig] = useState<{ key: string; direction: 'asc' | 'desc' }>({ key: 'actualUnits', direction: 'desc' });
|
||||
|
||||
// State for Column Filters (SKU, ASIN, Title, Line)
|
||||
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;
|
||||
});
|
||||
};
|
||||
|
||||
const activeMonths = useMemo(() => {
|
||||
if (filters.month && filters.month.length > 0) {
|
||||
return filters.month.map(m => m.split('-')[0]);
|
||||
@@ -262,6 +276,40 @@ const ForecastView: React.FC<ForecastViewProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
// Column Filters (Excel-style)
|
||||
if (Object.keys(columnFilters).length > 0) {
|
||||
(Object.entries(columnFilters) as [string, ColumnFilterCondition][]).forEach(([key, condition]) => {
|
||||
if (!condition) return;
|
||||
|
||||
if (condition.selectedValues && condition.selectedValues.length > 0) {
|
||||
result = result.filter(p => {
|
||||
const val = String((p as any)[key] || '');
|
||||
return condition.selectedValues?.includes(val);
|
||||
});
|
||||
}
|
||||
|
||||
if (condition.textFilter) {
|
||||
const { operator, value } = condition.textFilter;
|
||||
const lowerValue = value.toLowerCase();
|
||||
|
||||
result = result.filter(p => {
|
||||
const rowVal = String((p as any)[key] || '').toLowerCase();
|
||||
switch (operator) {
|
||||
case 'equals': return rowVal === lowerValue;
|
||||
case 'notEquals': return rowVal !== lowerValue;
|
||||
case 'contains': return rowVal.includes(lowerValue);
|
||||
case 'notContains': return !rowVal.includes(lowerValue);
|
||||
case 'startsWith': return rowVal.startsWith(lowerValue);
|
||||
case 'notStartsWith': return !rowVal.startsWith(lowerValue);
|
||||
case 'endsWith': return rowVal.endsWith(lowerValue);
|
||||
case 'notEndsWith': return !rowVal.endsWith(lowerValue);
|
||||
default: return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Apply WOC Filter
|
||||
if (wocFilter && wocFilter.length > 0 && vendorStockMap) {
|
||||
result = result.filter(p => {
|
||||
@@ -304,7 +352,7 @@ const ForecastView: React.FC<ForecastViewProps> = ({
|
||||
});
|
||||
|
||||
return result;
|
||||
}, [calculatedData, debouncedSearch, showOnlyTop50, top50Ranking, top50Mode, sortConfig, wocFilter, vendorStockMap]);
|
||||
}, [calculatedData, debouncedSearch, showOnlyTop50, top50Ranking, top50Mode, sortConfig, wocFilter, vendorStockMap, columnFilters]);
|
||||
|
||||
// [MOVED HERE] Global Summary - Now respects all filters including Search/WOC
|
||||
const globalSummary = useMemo(() => {
|
||||
@@ -492,8 +540,75 @@ const ForecastView: React.FC<ForecastViewProps> = ({
|
||||
<table className="w-full text-left border-collapse relative">
|
||||
<thead className="sticky top-0 z-20 bg-slate-950 shadow-sm text-[10px] font-black text-slate-500 uppercase tracking-wider">
|
||||
<tr>
|
||||
<th className="px-6 py-4 text-white w-[30%] cursor-pointer hover:bg-white/5 transition-colors" onClick={() => handleSort('sku')}>
|
||||
Product Info <SortIndicator column="sku" />
|
||||
<th className="px-6 py-4 text-left w-[30%]">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span
|
||||
className="text-[10px] font-black text-slate-400 uppercase tracking-widest cursor-pointer hover:text-white"
|
||||
onClick={() => handleSort('sku')}
|
||||
>
|
||||
Product Info <SortIndicator column="sku" />
|
||||
</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<ExcelFilter
|
||||
columnKey="sku"
|
||||
title="SKU"
|
||||
uniqueValues={Array.from(new Set(data.map(d => d.sku))).sort()}
|
||||
currentFilter={columnFilters['sku']}
|
||||
onFilterChange={handleColumnFilterChange}
|
||||
icon={<span className="text-[10px]">SKU</span>}
|
||||
/>
|
||||
<ExcelFilter
|
||||
columnKey="asin"
|
||||
title="ASIN"
|
||||
uniqueValues={Array.from(new Set(data.map(d => d.asin))).sort()}
|
||||
currentFilter={columnFilters['asin']}
|
||||
onFilterChange={handleColumnFilterChange}
|
||||
icon={<span className="text-[10px]">ASIN</span>}
|
||||
/>
|
||||
<ExcelFilter
|
||||
columnKey="title"
|
||||
title="Title"
|
||||
uniqueValues={Array.from(new Set(data.map(d => d.title))).sort()}
|
||||
currentFilter={columnFilters['title']}
|
||||
onFilterChange={handleColumnFilterChange}
|
||||
icon={<span className="text-[10px]">Title</span>}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<InColumnStockFilter
|
||||
currentFilters={stockFilter}
|
||||
onFilterChange={onStockFilterChange}
|
||||
title="Warehouse Stock"
|
||||
icon={<WarehouseIcon className="w-3" />}
|
||||
/>
|
||||
<InColumnStockFilter
|
||||
currentFilters={vendorStockFilter}
|
||||
onFilterChange={onVendorStockFilterChange}
|
||||
title="Vendor Stock"
|
||||
icon={<AmazonSmileIcon className="w-max" />}
|
||||
options={[
|
||||
'Out of Stock (0)',
|
||||
'In Stock (>0)',
|
||||
'In Stock (>20)',
|
||||
'Low Stock (<10)',
|
||||
]}
|
||||
/>
|
||||
<InColumnStockFilter
|
||||
currentFilters={wocFilter}
|
||||
onFilterChange={onWocFilterChange}
|
||||
title="Week Coverage"
|
||||
icon={<CoverageIcon className="w-3" />}
|
||||
options={[
|
||||
'< 4 Weeks',
|
||||
'> 4 Weeks',
|
||||
'Out of Stock',
|
||||
'Infinite Cover'
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
<th className="px-6 py-4 text-center w-[12%] cursor-pointer hover:bg-white/5 transition-colors" onClick={() => handleSort('annualForecast')}>
|
||||
Annual Forecast <SortIndicator column="annualForecast" />
|
||||
|
||||
Reference in New Issue
Block a user