import React, { useMemo, useState, useEffect } 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(''); const [showOnlyTop50, setShowOnlyTop50] = useState(false); const [top50Mode, setTop50Mode] = useState<'eu' | 'uk'>('eu'); // Auto-detect best Top 50 mode based on data useEffect(() => { const hasUK = data.some(p => p.line.toLowerCase().includes('uk') || (p.monthlyData && p.actualUnits > 0 && filters.customer.some(c => c.toLowerCase().includes('uk')))); const hasEU = data.some(p => !p.line.toLowerCase().includes('uk')); // Simplified detection: if we have a lot of EU products, default EU, otherwise check UK if (hasUK && !hasEU) setTop50Mode('uk'); }, [data, filters.customer]); // 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 periodForecast = 0; let periodActual = 0; let annualForecast = 0; let annualActual = 0; data.forEach(p => { annualForecast += p.annualForecast; p.monthlyData.forEach(md => { annualActual += md.actualUnits; if (activeMonths.includes(md.month)) { periodForecast += md.forecastUnits; periodActual += md.actualUnits; } }); }); const periodFulfillment = periodForecast > 0 ? (periodActual / periodForecast) * 100 : 0; const annualFulfillment = annualForecast > 0 ? (annualActual / annualForecast) * 100 : 0; return { periodForecast, periodActual, periodFulfillment, annualForecast, annualActual, annualFulfillment }; }, [data, activeMonths]); const filteredProducts = useMemo(() => { let result = data; if (searchTerm) { const s = searchTerm.toLowerCase(); result = result.filter(p => p.asin.toLowerCase().includes(s) || p.sku.toLowerCase().includes(s) || p.title.toLowerCase().includes(s) ); } if (showOnlyTop50 && top50Ranking) { const currentRankMap = top50Mode === 'eu' ? top50Ranking.eu : top50Ranking.uk; result = result.filter(p => currentRankMap.has(p.asin.toUpperCase())); } return result; }, [data, searchTerm, showOnlyTop50, top50Mode, top50Ranking]); const sortedProducts = useMemo(() => { const result = [...filteredProducts]; if (showOnlyTop50 && top50Ranking) { const currentRankMap = top50Mode === 'eu' ? top50Ranking.eu : top50Ranking.uk; result.sort((a, b) => { const rankA = currentRankMap.get(a.asin.toUpperCase()) || 999; const rankB = currentRankMap.get(b.asin.toUpperCase()) || 999; return rankA - rankB; }); } else { result.sort((a, b) => b.annualForecast - a.annualForecast); } return result; }, [filteredProducts, showOnlyTop50, top50Mode, top50Ranking]); return (
{/* Summary Cards */}
{/* Annual Forecast Card */}

Annual Forecast Total

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

Total Forecast (Period)

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

Total Actual Sales (Period)

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

Fulfillment (Period)

= 100 ? 'text-emerald-400' : 'text-indigo-400'}`}> {globalSummary.periodFulfillment.toFixed(1)}%
{/* Fulfillment Progress Bar */}
{/* Annual Fulfillment Card */}

Annual Fulfillment %

{globalSummary.annualFulfillment.toFixed(1)}%
{/* Main Trend Chart */}

Monthly Evolution: Forecast vs Actual

{/* Product Table */}

Product Performance Comparison

{/* Top 50 Toggle */}
{showOnlyTop50 && (
)}
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;