import React, { useMemo, useState } from 'react'; import { ProductForecastData, FilterState } from '../types'; import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, LineChart, Line, Legend, ComposedChart, Area } from 'recharts'; interface ForecastViewProps { data: ProductForecastData[]; filters: FilterState; top50Ranking?: { eu: Map; uk: Map; }; } const MONTH_ORDER = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; // Top 50 Badge Component (reused logic) // Top 50 Badge Component (matched to image) const Top50Badge: React.FC<{ rank: number; label: string; theme?: 'amber' | 'indigo' | 'blue' }> = ({ rank, label }) => { return (
🏆 {label} {rank}
); }; const ForecastView: React.FC = ({ data, filters, top50Ranking }) => { const [searchTerm, setSearchTerm] = useState(''); // Determine the "Current Month" based on available 2026 data const lastDataMonthIdx = useMemo(() => { let maxIdx = 0; // Default Jan data.forEach(p => { p.monthlyData.forEach((md, idx) => { if (md.actualUnits > 0 && idx > maxIdx) { maxIdx = idx; } }); }); return maxIdx; }, [data]); // Active months for "Monthly Forecast" calculation const activeMonths = useMemo(() => { if (filters.month && filters.month.length > 0) { // Map e.g. "Apr-24" or just "Apr" to the short name return filters.month.map(m => m.split('-')[0]); } // If no filter, use Jan to lastDataMonth return MONTH_ORDER.slice(0, lastDataMonthIdx + 1); }, [filters.month, lastDataMonthIdx]); // Global Aggregate Data const globalMonthlyData = useMemo(() => { return MONTH_ORDER.map(m => { let forecast = 0; let actual = 0; data.forEach(p => { const monthPoint = p.monthlyData.find(md => md.month === m); if (monthPoint) { forecast += monthPoint.forecastUnits; actual += monthPoint.actualUnits; } }); return { name: m, Forecast: forecast, Actual: actual }; }); }, [data]); const globalSummary = useMemo(() => { let totalForecast = 0; let totalActual = 0; data.forEach(p => { totalForecast += p.annualForecast; p.monthlyData.forEach(md => { totalActual += md.actualUnits; }); }); const fulfillment = totalForecast > 0 ? (totalActual / totalForecast) * 100 : 0; return { totalForecast, totalActual, fulfillment }; }, [data]); const filteredProducts = useMemo(() => { if (!searchTerm) return data; const s = searchTerm.toLowerCase(); return data.filter(p => p.asin.toLowerCase().includes(s) || p.sku.toLowerCase().includes(s) || p.title.toLowerCase().includes(s) ); }, [data, searchTerm]); const sortedProducts = useMemo(() => { return [...filteredProducts].sort((a, b) => b.annualForecast - a.annualForecast); }, [filteredProducts]); return (
{/* Summary Cards */}

Total Forecast 2026

{globalSummary.totalForecast.toLocaleString('de-DE')} Units

Total Actual Sales 2026

{globalSummary.totalActual.toLocaleString('de-DE')} Units

Global Fulfillment

= 100 ? 'text-emerald-400' : 'text-indigo-400'}`}> {globalSummary.fulfillment.toFixed(1)}%
{/* Fulfillment Progress Bar */}
{/* Main Trend Chart */}

Monthly Evolution: Forecast vs Actual

{/* Product Table */}

Product Performance Comparison

setSearchTerm(e.target.value)} className="bg-slate-950 border border-slate-700 rounded-lg px-10 py-2 text-sm text-slate-200 focus:outline-none focus:border-indigo-500 w-full md:w-80" />
{sortedProducts.map((p) => { const asin = p.asin.toUpperCase(); // Top 50 Badges logic const ranks = []; if (top50Ranking) { const rankEU = top50Ranking.eu.get(asin); const rankUK = top50Ranking.uk.get(asin); if (rankEU) ranks.push({ rank: rankEU, label: 'EU', theme: 'indigo' as const }); if (rankUK) ranks.push({ rank: rankUK, label: 'UK', theme: 'blue' as const }); } // Monthly Forecast Calculation const filteredForecast = p.monthlyData .filter(md => activeMonths.includes(md.month)) .reduce((acc, md) => acc + md.forecastUnits, 0); // Actual Sales for 2026 (Selected Period) const actualPeriod = p.monthlyData .filter(md => activeMonths.includes(md.month)) .reduce((acc, md) => acc + md.actualUnits, 0); // Total annual actual for achievement percentage const actualTotal = p.monthlyData.reduce((acc, md) => acc + md.actualUnits, 0); const totalAchievement = p.annualForecast > 0 ? (actualTotal / p.annualForecast) * 100 : 0; // YTD Achievement (Achievement of the period) const periodAchievement = filteredForecast > 0 ? (actualPeriod / filteredForecast) * 100 : 0; return ( ); })}
Product Info Forecast (Period) Actual Units Sales 2026 Fc 26 Achievement YTD Achievement
{/* ID Row */}
{ranks.map((r, i) => ( ))} {p.sku}
{p.asin}
{/* Title Row */}
{p.title}
{/* Line Row */}
{p.line}
{filteredForecast.toLocaleString('de-DE')} {actualPeriod.toLocaleString('de-DE')}
= 50 ? 'bg-emerald-500/10 text-emerald-400' : totalAchievement >= 10 ? 'bg-indigo-500/10 text-indigo-400' : 'bg-slate-800 text-slate-500'}`}> {totalAchievement.toFixed(1)}% of Annual {p.annualForecast.toLocaleString('de-DE')}
= 100 ? 'bg-emerald-500' : periodAchievement >= 50 ? 'bg-indigo-500' : 'bg-amber-500'}`} style={{ width: `${Math.min(100, periodAchievement)}%` }} >
= 100 ? 'text-emerald-400' : periodAchievement >= 50 ? 'text-indigo-400' : 'text-amber-400'}`}> {periodAchievement.toFixed(1)}%
); }; export default ForecastView;