diff --git a/components/ForecastView.tsx b/components/ForecastView.tsx index 974c0a2..79cfc0d 100644 --- a/components/ForecastView.tsx +++ b/components/ForecastView.tsx @@ -48,69 +48,94 @@ const ForecastRow: React.FC<{ } } - const monthlySales = activeMonths.map(m => (item.monthlyData && item.monthlyData[m]?.units) || 0); - const avgMonthly = monthlySales.length > 0 ? monthlySales.reduce((a, b) => a + b, 0) / activeMonths.length : 0; - const peakMonthly = monthlySales.length > 0 ? Math.max(...monthlySales) : 0; + // Calculations for new columns + const forecastPeriod = item.forecastUnits || 0; + const actualUnits = item.actualUnits || 0; + const annualForecast = item.annualForecast || 0; + + // FC 26 Achievement: Actual / Annual Forecast + const fcAchievement = annualForecast > 0 ? (actualUnits / annualForecast) * 100 : 0; + + // YTD Achievement: Actual / Period Forecast + // If activeMonths is basically "Year To Date" (which it usually is in this view unless filtered), this logic holds. + // If user selects specific months, it becomes "Period Achievement". + const ytdAchievement = forecastPeriod > 0 ? (actualUnits / forecastPeriod) * 100 : 0; + + let achievementColor = 'bg-slate-700'; + if (ytdAchievement >= 100) achievementColor = 'bg-emerald-500'; + else if (ytdAchievement >= 80) achievementColor = 'bg-emerald-400'; + else if (ytdAchievement >= 50) achievementColor = 'bg-amber-400'; + else achievementColor = 'bg-rose-500'; return ( - + + {/* Product Info */} -
-
+
+
{ranks.map((r, i) => ( ))} {item.sku} -
+
{item.asin}
- {item.title} + + {item.title} +
- {item.line} - {stockMap && ( - - )} + + {item.line} + + {/* WOC Indicator preserved */}
- - - {(item.actualUnits || 0).toLocaleString('de-DE')} - - - {(item.forecastUnits || 0).toLocaleString('de-DE')} - - -
- {(Math.round(avgMonthly) || 0).toLocaleString('de-DE')} - Peak: {(Math.round(peakMonthly) || 0).toLocaleString('de-DE')} -
- - -
- - ({ name: m, units: (item.monthlyData && item.monthlyData[m]?.units) || 0 }))}> - - - -
- - -
- = 80 ? 'text-emerald-400' : item.accuracy >= 50 ? 'text-amber-400' : 'text-rose-400'}`}> - {item.accuracy}% + {/* Forecast (Period) */} + + + {forecastPeriod.toLocaleString('de-DE')} -
-
= 80 ? 'bg-emerald-500' : item.accuracy >= 50 ? 'bg-amber-500' : 'bg-rose-500'}`} - style={{ width: `${item.accuracy}%` }} - /> + + + {/* Actual Units Sales 2026 */} + + + {actualUnits.toLocaleString('de-DE')} + + + + {/* FC 26 Achievement (Annual %) */} + +
+
+ {fcAchievement.toFixed(1)}% +
+ + OF ANNUAL {Math.round(annualForecast / 1000)}K +
-
- + + + {/* YTD Achievement (Period %) */} + +
+
+ = 100 ? 'text-emerald-400' : 'text-slate-300'}`}> + {ytdAchievement.toFixed(1)}% + +
+
+
+
+
+ ); }); @@ -257,40 +282,43 @@ const ForecastView: React.FC = ({ 'In Stock (>20)', 'Low Stock (<10)', ]} - /> -
-
- - Actuals - Forecast - Avg/Mo - Trend - Accuracy - - - - {finalFilteredData.slice(0, displayCount).map(p => ( - - ))} - - - {displayCount < finalFilteredData.length && ( -
- -
- )} -
-
-
- ); +
+
+ + + + + + + + + + + + {data.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
+ + {data.length === 0 && ( +
+

No forecast data found for current filters

+
+ )} +
+ + {/* Filter Panel (In-Column Stock Filter is handled inside components if needed, or we can add a toolbar later) */} +
+ ); }; -export default ForecastView; + export default ForecastView;