mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 14:35:22 +02:00
Optimize Forecast View performance and implement WOC filter across views
This commit is contained in:
@@ -50,6 +50,8 @@ const App: React.FC = () => {
|
|||||||
const [lastUpdated, setLastUpdated] = useState<string | null>(null);
|
const [lastUpdated, setLastUpdated] = useState<string | null>(null);
|
||||||
const [stockMap, setStockMap] = useState<Map<string, number>>(new Map());
|
const [stockMap, setStockMap] = useState<Map<string, number>>(new Map());
|
||||||
const [vendorStockMap, setVendorStockMap] = useState<Map<string, { eu: number; uk: number }>>(new Map());
|
const [vendorStockMap, setVendorStockMap] = useState<Map<string, { eu: number; uk: number }>>(new Map());
|
||||||
|
const [cachedForecastRecords, setCachedForecastRecords] = useState<ForecastRecord[]>([]);
|
||||||
|
const [lastForecastFile, setLastForecastFile] = useState<string | null>(null);
|
||||||
|
|
||||||
// Modal State
|
// Modal State
|
||||||
const [isDataModalOpen, setIsDataModalOpen] = useState(false);
|
const [isDataModalOpen, setIsDataModalOpen] = useState(false);
|
||||||
@@ -65,6 +67,7 @@ const App: React.FC = () => {
|
|||||||
week: [],
|
week: [],
|
||||||
stock: [],
|
stock: [],
|
||||||
vendorStock: [],
|
vendorStock: [],
|
||||||
|
woc: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const top50Mode = useMemo(() => {
|
const top50Mode = useMemo(() => {
|
||||||
@@ -200,12 +203,20 @@ const App: React.FC = () => {
|
|||||||
const isUK = activeFilters.customer.includes('Amazon UK');
|
const isUK = activeFilters.customer.includes('Amazon UK');
|
||||||
const filename = isUK ? '/fc UK 26.xlsx' : '/fc 26.xlsx';
|
const filename = isUK ? '/fc UK 26.xlsx' : '/fc 26.xlsx';
|
||||||
|
|
||||||
console.log(`[App] Fetching forecast from ${filename}...`);
|
let fcRecords = cachedForecastRecords;
|
||||||
const response = await fetch(filename);
|
|
||||||
if (!response.ok) throw new Error(`Forecast file ${filename} not found`);
|
|
||||||
|
|
||||||
const buffer = await response.arrayBuffer();
|
if (lastForecastFile !== filename || cachedForecastRecords.length === 0) {
|
||||||
const fcRecords = await processForecastExcel(buffer);
|
console.log(`[App] Fetching fresh forecast from ${filename}...`);
|
||||||
|
const response = await fetch(filename);
|
||||||
|
if (!response.ok) throw new Error(`Forecast file ${filename} not found`);
|
||||||
|
|
||||||
|
const buffer = await response.arrayBuffer();
|
||||||
|
fcRecords = await processForecastExcel(buffer);
|
||||||
|
setCachedForecastRecords(fcRecords);
|
||||||
|
setLastForecastFile(filename);
|
||||||
|
} else {
|
||||||
|
console.log(`[App] Using cached forecast for ${filename}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Build precise metadata map from GLOBAL sales records to ensure info is found even if filtered for market
|
// Build precise metadata map from GLOBAL sales records to ensure info is found even if filtered for market
|
||||||
const meta = new Map<string, { sku: string; title: string; line: string }>();
|
const meta = new Map<string, { sku: string; title: string; line: string }>();
|
||||||
@@ -221,11 +232,11 @@ const App: React.FC = () => {
|
|||||||
|
|
||||||
const viewData = calculateForecastViewData(sales, fcRecords, meta, activeFilters, velocityMap);
|
const viewData = calculateForecastViewData(sales, fcRecords, meta, activeFilters, velocityMap);
|
||||||
setForecastData(viewData);
|
setForecastData(viewData);
|
||||||
console.log('[App] Forecast loaded:', viewData.length, 'records');
|
console.log('[App] Forecast calculation complete:', viewData.length, 'records');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("Forecast fetch failed:", error);
|
console.warn("Forecast fetch failed:", error);
|
||||||
}
|
}
|
||||||
}, [velocityMap]); // Added velocityMap to dependencies
|
}, [velocityMap, cachedForecastRecords, lastForecastFile]);
|
||||||
|
|
||||||
// Update forecast when filters change
|
// Update forecast when filters change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -253,6 +264,7 @@ const App: React.FC = () => {
|
|||||||
week: [],
|
week: [],
|
||||||
stock: [],
|
stock: [],
|
||||||
vendorStock: [],
|
vendorStock: [],
|
||||||
|
woc: [],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -676,6 +688,8 @@ const App: React.FC = () => {
|
|||||||
onStockFilterChange={(s) => setFilters(prev => ({ ...prev, stock: s }))}
|
onStockFilterChange={(s) => setFilters(prev => ({ ...prev, stock: s }))}
|
||||||
vendorStockFilter={filters.vendorStock}
|
vendorStockFilter={filters.vendorStock}
|
||||||
onVendorStockFilterChange={(s) => setFilters(prev => ({ ...prev, vendorStock: s }))}
|
onVendorStockFilterChange={(s) => setFilters(prev => ({ ...prev, vendorStock: s }))}
|
||||||
|
wocFilter={filters.woc}
|
||||||
|
onWocFilterChange={(s) => setFilters(prev => ({ ...prev, woc: s }))}
|
||||||
customerFilters={filters.customer}
|
customerFilters={filters.customer}
|
||||||
top50Mode={filters.customer.includes('Amazon UK') ? 'uk' : 'eu'}
|
top50Mode={filters.customer.includes('Amazon UK') ? 'uk' : 'eu'}
|
||||||
top50Ranking={top50Ranking2025}
|
top50Ranking={top50Ranking2025}
|
||||||
@@ -702,6 +716,8 @@ const App: React.FC = () => {
|
|||||||
onStockFilterChange={(s) => setFilters(prev => ({ ...prev, stock: s }))}
|
onStockFilterChange={(s) => setFilters(prev => ({ ...prev, stock: s }))}
|
||||||
vendorStockFilter={filters.vendorStock}
|
vendorStockFilter={filters.vendorStock}
|
||||||
onVendorStockFilterChange={(s) => setFilters(prev => ({ ...prev, vendorStock: s }))}
|
onVendorStockFilterChange={(s) => setFilters(prev => ({ ...prev, vendorStock: s }))}
|
||||||
|
wocFilter={filters.woc}
|
||||||
|
onWocFilterChange={(s) => setFilters(prev => ({ ...prev, woc: s }))}
|
||||||
top50Mode={filters.customer.includes('Amazon UK') ? 'uk' : 'eu'}
|
top50Mode={filters.customer.includes('Amazon UK') ? 'uk' : 'eu'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -719,6 +735,8 @@ const App: React.FC = () => {
|
|||||||
onStockFilterChange={(s) => setFilters(prev => ({ ...prev, stock: s }))}
|
onStockFilterChange={(s) => setFilters(prev => ({ ...prev, stock: s }))}
|
||||||
vendorStockFilter={filters.vendorStock}
|
vendorStockFilter={filters.vendorStock}
|
||||||
onVendorStockFilterChange={(s) => setFilters(prev => ({ ...prev, vendorStock: s }))}
|
onVendorStockFilterChange={(s) => setFilters(prev => ({ ...prev, vendorStock: s }))}
|
||||||
|
wocFilter={filters.woc}
|
||||||
|
onWocFilterChange={(s) => setFilters(prev => ({ ...prev, woc: s }))}
|
||||||
top50Mode={filters.customer.includes('Amazon UK') ? 'uk' : 'eu'}
|
top50Mode={filters.customer.includes('Amazon UK') ? 'uk' : 'eu'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Binary file not shown.
@@ -21,6 +21,8 @@ interface AdsPerformanceProps {
|
|||||||
vendorStockMap?: Map<string, { eu: number; uk: number }>;
|
vendorStockMap?: Map<string, { eu: number; uk: number }>;
|
||||||
vendorStockFilter: string[];
|
vendorStockFilter: string[];
|
||||||
onVendorStockFilterChange: (newFilters: string[]) => void;
|
onVendorStockFilterChange: (newFilters: string[]) => void;
|
||||||
|
wocFilter: string[];
|
||||||
|
onWocFilterChange: (newFilters: string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type SortKey = keyof CombinedKPIs | 'acos' | 'roas' | 'tacos' | 'ctr' | 'cpc' | 'cvrUnits';
|
type SortKey = keyof CombinedKPIs | 'acos' | 'roas' | 'tacos' | 'ctr' | 'cpc' | 'cvrUnits';
|
||||||
@@ -106,6 +108,8 @@ const AdsPerformance: React.FC<AdsPerformanceProps> = ({
|
|||||||
onStockFilterChange,
|
onStockFilterChange,
|
||||||
vendorStockFilter,
|
vendorStockFilter,
|
||||||
onVendorStockFilterChange,
|
onVendorStockFilterChange,
|
||||||
|
wocFilter,
|
||||||
|
onWocFilterChange,
|
||||||
top50Mode
|
top50Mode
|
||||||
}) => {
|
}) => {
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
@@ -258,6 +262,33 @@ const AdsPerformance: React.FC<AdsPerformanceProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply WOC Filter
|
||||||
|
if (wocFilter && wocFilter.length > 0 && vendorStockMap) {
|
||||||
|
result = result.filter(item => {
|
||||||
|
const asin = item.asin.trim().toUpperCase();
|
||||||
|
const stockData = vendorStockMap.get(asin);
|
||||||
|
if (!stockData) return false;
|
||||||
|
|
||||||
|
const stock = top50Mode === 'uk' ? stockData.uk : stockData.eu;
|
||||||
|
const velocity = item.avgWeeklySales || 0;
|
||||||
|
|
||||||
|
let woc: number = 0;
|
||||||
|
if (velocity > 0) {
|
||||||
|
woc = stock / velocity;
|
||||||
|
} else if (stock > 0) {
|
||||||
|
woc = 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
return wocFilter.some(f => {
|
||||||
|
if (f === '< 4 Weeks') return woc < 4;
|
||||||
|
if (f === '> 4 Weeks') return woc >= 4;
|
||||||
|
if (f === 'Out of Stock') return woc === 0;
|
||||||
|
if (f === 'Infinite Cover') return woc === 999;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (showOnlyTop50 && top50Ranking) {
|
if (showOnlyTop50 && top50Ranking) {
|
||||||
result.sort((a, b) => {
|
result.sort((a, b) => {
|
||||||
const asinA = a.asin.trim().toUpperCase();
|
const asinA = a.asin.trim().toUpperCase();
|
||||||
@@ -408,6 +439,17 @@ const AdsPerformance: React.FC<AdsPerformanceProps> = ({
|
|||||||
'Low Stock (<10)',
|
'Low Stock (<10)',
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
<InColumnStockFilter
|
||||||
|
currentFilters={wocFilter}
|
||||||
|
onFilterChange={onWocFilterChange}
|
||||||
|
title="Week Coverage"
|
||||||
|
options={[
|
||||||
|
'< 4 Weeks',
|
||||||
|
'> 4 Weeks',
|
||||||
|
'Out of Stock',
|
||||||
|
'Infinite Cover'
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ interface ForecastViewProps {
|
|||||||
onStockFilterChange: (newFilters: string[]) => void;
|
onStockFilterChange: (newFilters: string[]) => void;
|
||||||
vendorStockFilter: string[];
|
vendorStockFilter: string[];
|
||||||
onVendorStockFilterChange: (newFilters: string[]) => void;
|
onVendorStockFilterChange: (newFilters: string[]) => void;
|
||||||
|
wocFilter: string[];
|
||||||
|
onWocFilterChange: (newFilters: string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MONTH_ORDER = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
const MONTH_ORDER = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||||
@@ -169,10 +171,20 @@ const ForecastView: React.FC<ForecastViewProps> = ({
|
|||||||
onStockFilterChange,
|
onStockFilterChange,
|
||||||
vendorStockFilter,
|
vendorStockFilter,
|
||||||
onVendorStockFilterChange,
|
onVendorStockFilterChange,
|
||||||
|
wocFilter,
|
||||||
|
onWocFilterChange,
|
||||||
top50Mode
|
top50Mode
|
||||||
}) => {
|
}) => {
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const debouncedSearch = useMemo(() => searchTerm.toLowerCase(), [searchTerm]);
|
const [debouncedSearch, setDebouncedSearch] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setDebouncedSearch(searchTerm.toLowerCase());
|
||||||
|
}, 300);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [searchTerm]);
|
||||||
|
|
||||||
const [showOnlyTop50, setShowOnlyTop50] = useState(false);
|
const [showOnlyTop50, setShowOnlyTop50] = useState(false);
|
||||||
const [displayCount, setDisplayCount] = useState(50);
|
const [displayCount, setDisplayCount] = useState(50);
|
||||||
const [sortConfig, setSortConfig] = useState<{ key: string; direction: 'asc' | 'desc' }>({ key: 'actualUnits', direction: 'desc' });
|
const [sortConfig, setSortConfig] = useState<{ key: string; direction: 'asc' | 'desc' }>({ key: 'actualUnits', direction: 'desc' });
|
||||||
@@ -232,8 +244,8 @@ const ForecastView: React.FC<ForecastViewProps> = ({
|
|||||||
return Array.from(dataMap.values());
|
return Array.from(dataMap.values());
|
||||||
}, [baseFilteredData]);
|
}, [baseFilteredData]);
|
||||||
|
|
||||||
const processedData = useMemo(() => {
|
const calculatedData = useMemo(() => {
|
||||||
let result = baseFilteredData.map(item => {
|
return baseFilteredData.map(item => {
|
||||||
const forecastPeriod = activeMonths.reduce((sum, month) => {
|
const forecastPeriod = activeMonths.reduce((sum, month) => {
|
||||||
return sum + (item.monthlyData?.[month]?.forecastUnits || 0);
|
return sum + (item.monthlyData?.[month]?.forecastUnits || 0);
|
||||||
}, 0);
|
}, 0);
|
||||||
@@ -247,6 +259,10 @@ const ForecastView: React.FC<ForecastViewProps> = ({
|
|||||||
fcAchievement
|
fcAchievement
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
}, [baseFilteredData, activeMonths]);
|
||||||
|
|
||||||
|
const processedData = useMemo(() => {
|
||||||
|
let result = [...calculatedData];
|
||||||
|
|
||||||
if (showOnlyTop50 && top50Ranking) {
|
if (showOnlyTop50 && top50Ranking) {
|
||||||
const currentRankMap = top50Mode === 'eu' ? top50Ranking.eu : top50Ranking.uk;
|
const currentRankMap = top50Mode === 'eu' ? top50Ranking.eu : top50Ranking.uk;
|
||||||
@@ -261,6 +277,33 @@ const ForecastView: React.FC<ForecastViewProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply WOC Filter
|
||||||
|
if (wocFilter && wocFilter.length > 0 && vendorStockMap) {
|
||||||
|
result = result.filter(p => {
|
||||||
|
const asin = p.asin.trim().toUpperCase();
|
||||||
|
const stockData = vendorStockMap.get(asin);
|
||||||
|
if (!stockData) return false;
|
||||||
|
|
||||||
|
const stock = top50Mode === 'uk' ? stockData.uk : stockData.eu;
|
||||||
|
const velocity = p.avgWeeklySales || 0;
|
||||||
|
|
||||||
|
let woc: number = 0;
|
||||||
|
if (velocity > 0) {
|
||||||
|
woc = stock / velocity;
|
||||||
|
} else if (stock > 0) {
|
||||||
|
woc = 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
return wocFilter.some(f => {
|
||||||
|
if (f === '< 4 Weeks') return woc < 4;
|
||||||
|
if (f === '> 4 Weeks') return woc >= 4;
|
||||||
|
if (f === 'Out of Stock') return woc === 0;
|
||||||
|
if (f === 'Infinite Cover') return woc === 999;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Apply Sorting
|
// Apply Sorting
|
||||||
const { key, direction } = sortConfig;
|
const { key, direction } = sortConfig;
|
||||||
result.sort((a: any, b: any) => {
|
result.sort((a: any, b: any) => {
|
||||||
@@ -276,7 +319,7 @@ const ForecastView: React.FC<ForecastViewProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}, [baseFilteredData, debouncedSearch, showOnlyTop50, top50Ranking, top50Mode, sortConfig, activeMonths]);
|
}, [calculatedData, debouncedSearch, showOnlyTop50, top50Ranking, top50Mode, sortConfig]);
|
||||||
|
|
||||||
const paginatedData = useMemo(() => processedData.slice(0, displayCount), [processedData, displayCount]);
|
const paginatedData = useMemo(() => processedData.slice(0, displayCount), [processedData, displayCount]);
|
||||||
|
|
||||||
@@ -393,6 +436,17 @@ const ForecastView: React.FC<ForecastViewProps> = ({
|
|||||||
className="bg-slate-950 border border-white/10 rounded-xl px-4 py-2 text-sm text-white focus:outline-none w-64"
|
className="bg-slate-950 border border-white/10 rounded-xl px-4 py-2 text-sm text-white focus:outline-none w-64"
|
||||||
/>
|
/>
|
||||||
<button onClick={handleExportExcel} className="p-2 bg-slate-800 hover:bg-slate-700 text-white rounded-xl border border-white/5 transition-all"><DownloadIcon /></button>
|
<button onClick={handleExportExcel} className="p-2 bg-slate-800 hover:bg-slate-700 text-white rounded-xl border border-white/5 transition-all"><DownloadIcon /></button>
|
||||||
|
<InColumnStockFilter
|
||||||
|
currentFilters={wocFilter}
|
||||||
|
onFilterChange={onWocFilterChange}
|
||||||
|
title="Week Coverage"
|
||||||
|
options={[
|
||||||
|
'< 4 Weeks',
|
||||||
|
'> 4 Weeks',
|
||||||
|
'Out of Stock',
|
||||||
|
'Infinite Cover'
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ interface WeeklyGridProps {
|
|||||||
vendorStockMap?: Map<string, { eu: number; uk: number }>;
|
vendorStockMap?: Map<string, { eu: number; uk: number }>;
|
||||||
vendorStockFilter: string[];
|
vendorStockFilter: string[];
|
||||||
onVendorStockFilterChange: (newFilters: string[]) => void;
|
onVendorStockFilterChange: (newFilters: string[]) => void;
|
||||||
|
wocFilter: string[];
|
||||||
|
onWocFilterChange: (newFilters: string[]) => void;
|
||||||
velocityMap?: Map<string, number>;
|
velocityMap?: Map<string, number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,6 +154,8 @@ const WeeklyGrid: React.FC<WeeklyGridProps & { top50Mode: 'eu' | 'uk' }> = ({
|
|||||||
onStockFilterChange,
|
onStockFilterChange,
|
||||||
vendorStockFilter,
|
vendorStockFilter,
|
||||||
onVendorStockFilterChange,
|
onVendorStockFilterChange,
|
||||||
|
wocFilter,
|
||||||
|
onWocFilterChange,
|
||||||
customerFilters,
|
customerFilters,
|
||||||
top50Mode,
|
top50Mode,
|
||||||
velocityMap
|
velocityMap
|
||||||
@@ -272,8 +276,35 @@ const WeeklyGrid: React.FC<WeeklyGridProps & { top50Mode: 'eu' | 'uk' }> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WOC Filter
|
||||||
|
if (wocFilter && wocFilter.length > 0 && velocityMap && vendorStockMap) {
|
||||||
|
result = result.filter(r => {
|
||||||
|
const asin = r.asin.trim().toUpperCase();
|
||||||
|
const stockData = vendorStockMap.get(asin);
|
||||||
|
if (!stockData) return false;
|
||||||
|
|
||||||
|
const stock = top50Mode === 'uk' ? stockData.uk : stockData.eu;
|
||||||
|
const velocity = velocityMap.get(asin) || 0;
|
||||||
|
|
||||||
|
let woc: number = 0;
|
||||||
|
if (velocity > 0) {
|
||||||
|
woc = stock / velocity;
|
||||||
|
} else if (stock > 0) {
|
||||||
|
woc = 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
return wocFilter.some(f => {
|
||||||
|
if (f === '< 4 Weeks') return woc < 4;
|
||||||
|
if (f === '> 4 Weeks') return woc >= 4;
|
||||||
|
if (f === 'Out of Stock') return woc === 0;
|
||||||
|
if (f === 'Infinite Cover') return woc === 999;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}, [rows, debouncedSearch, showOnlyTop50, top50Ranking, growthFilterMode, growthThreshold, sortConfig, weeks, top50Mode]);
|
}, [rows, debouncedSearch, showOnlyTop50, top50Ranking, growthFilterMode, growthThreshold, sortConfig, weeks, top50Mode, wocFilter, velocityMap, vendorStockMap]);
|
||||||
|
|
||||||
// 2. Sort results
|
// 2. Sort results
|
||||||
const sortedRows = useMemo(() => {
|
const sortedRows = useMemo(() => {
|
||||||
@@ -499,6 +530,17 @@ const WeeklyGrid: React.FC<WeeklyGridProps & { top50Mode: 'eu' | 'uk' }> = ({
|
|||||||
'Low Stock (<10)',
|
'Low Stock (<10)',
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
<InColumnStockFilter
|
||||||
|
currentFilters={wocFilter}
|
||||||
|
onFilterChange={onWocFilterChange}
|
||||||
|
title="Week Coverage"
|
||||||
|
options={[
|
||||||
|
'< 4 Weeks',
|
||||||
|
'> 4 Weeks',
|
||||||
|
'Out of Stock',
|
||||||
|
'Infinite Cover'
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{sortConfig?.metric === 'rank' && (
|
{sortConfig?.metric === 'rank' && (
|
||||||
<span className="text-amber-500 font-black text-sm animate-bounce-subtle">
|
<span className="text-amber-500 font-black text-sm animate-bounce-subtle">
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export interface FilterState {
|
|||||||
week: string[]; // Added Week filter
|
week: string[]; // Added Week filter
|
||||||
stock: string[]; // Added Stock filter
|
stock: string[]; // Added Stock filter
|
||||||
vendorStock: string[]; // Added Vendor Stock filter
|
vendorStock: string[]; // Added Vendor Stock filter
|
||||||
|
woc: string[]; // Added Weeks of Coverage filter
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GrowthMetric {
|
export interface GrowthMetric {
|
||||||
|
|||||||
Reference in New Issue
Block a user