From 1886554a310cdd915ec11b44d7563b7ba9d6ee57 Mon Sep 17 00:00:00 2001 From: Christian Vidal Wolf Date: Wed, 28 Jan 2026 12:35:40 +0100 Subject: [PATCH] Fix Weekly Grid WOC and Restore Forecast View Graph/UI --- App.tsx | 1 + components/ForecastView.tsx | 182 +++++++++++++++++++++++++++++------- components/WeeklyGrid.tsx | 15 ++- 3 files changed, 161 insertions(+), 37 deletions(-) diff --git a/App.tsx b/App.tsx index a72629d..786df01 100644 --- a/App.tsx +++ b/App.tsx @@ -674,6 +674,7 @@ const App: React.FC = () => { customerFilters={filters.customer} top50Mode={filters.customer.includes('Amazon UK') ? 'uk' : 'eu'} top50Ranking={top50Ranking2025} + velocityMap={velocityMap} /> )} diff --git a/components/ForecastView.tsx b/components/ForecastView.tsx index 25b397d..bffa74a 100644 --- a/components/ForecastView.tsx +++ b/components/ForecastView.tsx @@ -181,9 +181,26 @@ const ForecastView: React.FC = ({ const globalSummary = useMemo(() => { return baseFilteredData.reduce((acc, curr) => ({ - actualUnits: acc.actualUnits + curr.actualUnits, - forecastUnits: acc.forecastUnits + curr.forecastUnits, - }), { actualUnits: 0, forecastUnits: 0 }); + actualUnits: acc.actualUnits + (curr.actualUnits || 0), + forecastUnits: acc.forecastUnits + (curr.forecastUnits || 0), + annualForecast: acc.annualForecast + (curr.annualForecast || 0), + }), { actualUnits: 0, forecastUnits: 0, annualForecast: 0 }); + }, [baseFilteredData]); + + const chartData = useMemo(() => { + const dataMap = new Map(); + MONTH_ORDER.forEach(m => dataMap.set(m, { name: m, actual: 0, forecast: 0 })); + + baseFilteredData.forEach(item => { + item.monthlyData?.forEach(m => { + const entry = dataMap.get(m.month); + if (entry) { + entry.actual += m.actual || 0; + entry.forecast += m.forecast || 0; + } + }); + }); + return Array.from(dataMap.values()); }, [baseFilteredData]); const finalFilteredData = useMemo(() => { @@ -216,38 +233,135 @@ const ForecastView: React.FC = ({ }, [finalFilteredData]); return ( -
-
- - - - - - - - - - - - {finalFilteredData.map(item => ( - 0 ? filters.month : MONTH_ORDER} - top50Ranking={top50Ranking} - top50Mode={top50Mode} - stockMap={stockMap} - vendorStockMap={vendorStockMap} - /> - ))} - -
Product InfoForecast (Period)Actual Units Sales 2026FC 26 AchievementYTD Achievement
- - {finalFilteredData.length === 0 && ( -
-

No forecast data found for current filters

+
+ {/* Top Section: Metrics & Graph */} +
+ {/* Metrics Cards */} +
+ {/* Annual Forecast */} +
+ Annual Forecast Total +
{(globalSummary.annualForecast || 0).toLocaleString('de-DE')} Units
- )} + {/* Period Forecast */} +
+ Total Forecast (Period) +
{(globalSummary.forecastUnits || 0).toLocaleString('de-DE')} Units
+
+ {/* Actual Sales */} +
+ Total Actual Sales (Period) +
{(globalSummary.actualUnits || 0).toLocaleString('de-DE')} Units
+
+ {/* Fulfillment */} +
+
+ Fulfillment (Period) +
{globalSummary.forecastUnits > 0 ? ((globalSummary.actualUnits / globalSummary.forecastUnits) * 100).toFixed(1) : '0.0'}%
+
+
0 ? ((globalSummary.actualUnits / globalSummary.forecastUnits) * 100) : 0)}%` }} /> +
+
+
+ Annual Fulfillment % +
{globalSummary.annualForecast > 0 ? ((globalSummary.actualUnits / globalSummary.annualForecast) * 100).toFixed(1) : '0.0'}%
+
+
+
+ + {/* Graph */} +
+

+ Monthly Evolution: Forecast vs Actual +

+
+ + + + + + + + + + + + + + + + + + + + + +
+
+
+ + {/* Bottom Section: Table */} +
+
+

Product Performance Comparison

+
+ {top50Ranking && (top50Ranking.eu.size > 0 || top50Ranking.uk.size > 0) && ( +
+ +
+ )} + setSearchTerm(e.target.value)} + className="bg-slate-950 border border-white/10 rounded-xl px-4 py-2 text-sm text-white focus:outline-none w-64" + /> + +
+
+ +
+ + + + + + + + + + + + {finalFilteredData.map(item => ( + 0 ? filters.month : MONTH_ORDER} + top50Ranking={top50Ranking} + top50Mode={top50Mode} + stockMap={stockMap} + vendorStockMap={vendorStockMap} + /> + ))} + +
Product InfoForecast (Period)Actual Units Sales 2026FC 26 AchievementYTD Achievement
+ + {finalFilteredData.length === 0 && ( +
+

No forecast data found for current filters

+
+ )} +
); diff --git a/components/WeeklyGrid.tsx b/components/WeeklyGrid.tsx index 18ec506..990803f 100644 --- a/components/WeeklyGrid.tsx +++ b/components/WeeklyGrid.tsx @@ -21,6 +21,7 @@ interface WeeklyGridProps { vendorStockMap?: Map; vendorStockFilter: string[]; onVendorStockFilterChange: (newFilters: string[]) => void; + velocityMap?: Map; } type SortConfig = { @@ -53,7 +54,8 @@ const WeeklyRow: React.FC<{ renderGrowth: (current: number, previous: number) => React.ReactNode; customerFilters: string[]; vendorStockMap?: Map; -}> = React.memo(({ row, weeks, onDrillDown, stockMap, top50Ranking, top50Mode, sortConfig, renderGrowth, customerFilters, vendorStockMap }) => { + velocityMap?: Map; +}> = React.memo(({ row, weeks, onDrillDown, stockMap, top50Ranking, top50Mode, sortConfig, renderGrowth, customerFilters, vendorStockMap, velocityMap }) => { const ranks: { rank: number; label: string; theme: 'amber' | 'blue' | 'indigo' }[] = []; const asin = row.asin.trim().toUpperCase(); @@ -90,7 +92,12 @@ const WeeklyRow: React.FC<{ {stockMap && ( )} - +
{row.line}
@@ -146,7 +153,8 @@ const WeeklyGrid: React.FC = ({ vendorStockFilter, onVendorStockFilterChange, customerFilters, - top50Mode + top50Mode, + velocityMap }) => { // Pivot data - memoized const { rows, weeks: allWeeks } = useMemo(() => pivotWeeklySalesData(data), [data]); @@ -583,6 +591,7 @@ const WeeklyGrid: React.FC = ({ sortConfig={sortConfig} renderGrowth={renderGrowth} customerFilters={customerFilters} + velocityMap={velocityMap} /> ))} {displayCount < sortedRows.length && (