"use client"; import { useState, useEffect, memo, useMemo, useCallback } from "react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { TrendingUp, TrendingDown, Package, DollarSign, AlertTriangle, RefreshCw, Calendar, BarChart3, Brain, Layers, Zap, Info, Download, } from "lucide-react"; import { useToast } from "@/hooks/use-toast"; import { Skeleton } from "@/components/ui/skeleton"; import CountUp from "react-countup"; import { getPredictionsOverviewWithStore, getStockPredictionsWithStore, type PredictionsOverview, type StockPredictionsResponse, } from "@/lib/services/analytics-service"; import { formatGBP } from "@/utils/format"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { format } from "date-fns"; import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip as RechartsTooltip, ResponsiveContainer, } from "recharts"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Slider } from "@/components/ui/slider"; interface PredictionsChartProps { timeRange?: number; } export default function PredictionsChart({ timeRange = 90, }: PredictionsChartProps) { const [predictions, setPredictions] = useState( null, ); const [baselinePredictions, setBaselinePredictions] = useState( null, ); // Batch data holds all pre-cached predictions for instant switching const [batchData, setBatchData] = useState<{ [horizon: string]: { [simulationFactor: string]: PredictionsOverview; }; } | null>(null); const [stockPredictions, setStockPredictions] = useState(null); const [loading, setLoading] = useState(true); const [isSimulating, setIsSimulating] = useState(false); const [daysAhead, setDaysAhead] = useState(7); const [activeTab, setActiveTab] = useState<"overview" | "stock">("overview"); const [simulationFactor, setSimulationFactor] = useState(0); const [committedSimulationFactor, setCommittedSimulationFactor] = useState(0); const { toast } = useToast(); // Fetch all predictions in batch (for instant client-side switching) const fetchBatchData = async () => { try { setLoading(true); const { getBatchPredictionsWithStore } = await import("@/lib/services/analytics-service"); const [batchResponse, stock] = await Promise.all([ getBatchPredictionsWithStore(timeRange), getStockPredictionsWithStore(timeRange), ]); if (batchResponse.success && batchResponse.predictions) { setBatchData(batchResponse.predictions); // Set initial predictions from batch const horizonData = batchResponse.predictions[daysAhead.toString()]; if (horizonData) { const baseline = horizonData["0"]; if (baseline) { setBaselinePredictions(baseline); setPredictions(baseline); } } } else { // Fallback to single request if batch not available const overview = await getPredictionsOverviewWithStore(daysAhead, timeRange, 0); setBaselinePredictions(overview); setPredictions(overview); } setStockPredictions(stock); } catch (error) { console.error("Error fetching predictions:", error); toast({ title: "Error", description: "Failed to load predictions", variant: "destructive", }); } finally { setLoading(false); } }; // Switch predictions from batch data (no API call!) const switchPredictions = useCallback((horizon: number, simFactor: number) => { if (!batchData) return; const horizonData = batchData[horizon.toString()]; if (!horizonData) return; // Simulation factor is stored as decimal (e.g., 0.1 for 10%) const simKey = (simFactor / 100).toString(); const newPrediction = horizonData[simKey]; if (newPrediction) { setPredictions(newPrediction); if (simFactor === 0) { setBaselinePredictions(newPrediction); } } }, [batchData]); // Fetch batch data on initial load or when timeRange changes useEffect(() => { fetchBatchData(); setCommittedSimulationFactor(0); setSimulationFactor(0); }, [timeRange]); // Auto-adjust daysAhead if it exceeds historical timeRange useEffect(() => { if (daysAhead > timeRange) { setDaysAhead(timeRange); } }, [timeRange, daysAhead]); // Switch predictions when daysAhead changes (instant, from batch) useEffect(() => { if (batchData) { switchPredictions(daysAhead, committedSimulationFactor); } }, [daysAhead, batchData, switchPredictions]); // Switch predictions when simulation factor changes (instant, from batch) useEffect(() => { if (batchData) { switchPredictions(daysAhead, committedSimulationFactor); } }, [committedSimulationFactor, batchData, switchPredictions]); const getConfidenceColor = (confidence: string) => { switch (confidence) { case "very_high": return "bg-emerald-600/20 text-emerald-700 dark:text-emerald-400 border-emerald-600/30"; case "high": return "bg-green-500/10 text-green-700 dark:text-green-400"; case "medium": return "bg-yellow-500/10 text-yellow-700 dark:text-yellow-400"; case "low": return "bg-red-500/10 text-red-700 dark:text-red-400"; default: return "bg-gray-500/10 text-gray-700 dark:text-gray-400"; } }; const getConfidenceLabel = (confidence: string) => { switch (confidence) { case "very_high": return "Very High"; case "high": return "High"; case "medium": return "Medium"; case "low": return "Low"; default: return confidence; } }; // Combine baseline and simulated data for overlay chart const chartData = useMemo(() => { if (!predictions?.sales?.dailyPredictions) return []; const baselineData = baselinePredictions?.sales?.dailyPredictions || []; const simulatedDailyData = predictions?.sales?.dailyPredictions || []; return simulatedDailyData.map((d: any, idx: number) => ({ ...d, formattedDate: format(new Date(d.date), "MMM d"), simulated: d.predicted, baseline: baselineData[idx]?.predicted ?? d.predicted, orders: d.predictedOrders || 0, })); }, [predictions, baselinePredictions]); // Keep simulatedData for export compatibility const simulatedData = chartData; const handleExportCSV = () => { if (!simulatedData.length) return; // Create CSV headers const headers = ["Date", "Baseline Revenue", "Simulated Revenue", "Orders", "Confidence"]; // Create CSV rows const rows = simulatedData.map(d => [ format(new Date(d.date), "yyyy-MM-dd"), d.baseline?.toFixed(2) || "", d.simulated?.toFixed(2) || "", d.orders || "", predictions?.sales?.confidence || "unknown" ]); // Combine headers and rows const csvContent = [ headers.join(","), ...rows.map(row => row.join(",")) ].join("\n"); // Create download link const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.setAttribute("href", url); link.setAttribute("download", `predictions_export_${format(new Date(), "yyyyMMdd")}.csv`); link.style.visibility = "hidden"; document.body.appendChild(link); link.click(); document.body.removeChild(link); }; if (loading) { return (
); } if (!predictions) { return ( Predictions Forecast future sales, demand, and stock levels

No prediction data available. Need more historical data.

); } return (
Predictions & Forecasting {predictions?.sales?.aiModel?.used ? "AI neural network + statistical models for sales, demand, and inventory" : "AI-powered predictions for sales, demand, and inventory"}
{activeTab === "overview" && (
{/* Sales Predictions */}
Revenue Prediction {predictions?.sales?.predicted !== null && predictions?.sales?.predicted !== undefined ? (

Predicted daily average revenue for the next {daysAhead} days

{getConfidenceLabel(predictions?.sales?.confidence || "low")} Confidence {predictions?.sales?.confidenceScore !== undefined && ( ({Math.round((predictions?.sales?.confidenceScore || 0) * 100)}%) )}

Based on data consistency, historical accuracy, and model agreement

{predictions?.sales?.aiModel?.used && ( 🤖 AI Powered {predictions?.sales?.aiModel?.modelAccuracy !== undefined && ( ({Math.round((predictions?.sales?.aiModel?.modelAccuracy || 0) * 100)}%) )}

Deep Learning Ensemble Model

This percentage indicates how well the AI has learned your specific sales patterns.

Scores above 90% are optimal—100% is avoided to prevent "memorizing" the past and ensure the model remains flexible for future shifts.

)} {predictions?.sales?.trend && ( {predictions?.sales?.trend?.direction === "up" && ( )} {predictions?.sales?.trend?.direction === "down" && ( )} {predictions?.sales?.trend?.direction === "up" ? "Trending Up" : predictions?.sales?.trend?.direction === "down" ? "Trending Down" : "Stable"}

Direction of the recent sales trend (slope analysis)

)} Next {daysAhead} days
{predictions?.sales?.predictedOrders && (
~{Math.round(predictions?.sales?.predictedOrders || 0)}{" "} orders
)} {!predictions?.sales?.confidenceIntervals && predictions?.sales?.minPrediction && predictions?.sales?.maxPrediction && (
Range: {formatGBP(predictions?.sales?.minPrediction || 0)} -{" "} {formatGBP(predictions?.sales?.maxPrediction || 0)}
)}
) : (
{predictions?.sales?.message || "Insufficient data for prediction"}
)}
{/* Model Intelligence Card */} Model Intelligence

Technical details about the active prediction model

Architecture Hybrid Ensemble (Deep Learning)

Combines LSTM Neural Networks with Statistical Methods (Holt-Winters, ARIMA)

{stockPredictions?.predictions && (
Features Multi-Feature Enabled
)}
Optimization Performance Tuned
Model automatically retrains with new sales data.
Prediction Accuracy Warning These predictions are estimates based on historical sales data. Actual results may vary due to external factors, market conditions, and unforeseen events. Use these insights as a guide, not a guarantee. {/* Daily Predictions Chart */} {predictions?.sales?.dailyPredictions && predictions?.sales?.dailyPredictions.length > 0 && (
Scenario Lab Adjust variables to see how traffic shifts impact your bottom line.
Traffic Simulation

Simulate traffic growth or decline to see how it might impact your future revenue and order volume.

setSimulationFactor(val[0])} onValueCommit={(val) => setCommittedSimulationFactor(val[0])} className="w-full flex-1" /> 0 ? "text-emerald-400 border-emerald-500/30 bg-emerald-500/10" : simulationFactor < 0 ? "text-rose-400 border-rose-500/30 bg-rose-500/10" : "text-primary/60"}`}> {simulationFactor > 0 ? "+" : ""}{simulationFactor}%
{(simulationFactor !== 0 || committedSimulationFactor !== 0) && ( )}
{/* Legend / Key */}
Baseline Forecast
{committedSimulationFactor !== 0 && (
Simulated Scenario
)}
{isSimulating && (
Running Neural Simulation...
)} `£${value}`} /> { if (active && payload?.length) { const data = payload[0].payload; return (

{data.formattedDate}

Baseline: {formatGBP(data.baseline)}
{committedSimulationFactor !== 0 && (
Simulated:
{formatGBP(data.simulated)} data.baseline ? 'text-emerald-500' : 'text-rose-500'}`}> {data.simulated > data.baseline ? '▴' : '▾'} {Math.abs(((data.simulated / data.baseline - 1) * 100)).toFixed(1)}%
)}
Est. Orders: {Math.round(data.orders)}
); } return null; }} /> {/* Always show baseline */} {/* Show simulated line when simulation is active */} {committedSimulationFactor !== 0 && ( )}
)}
)} {activeTab === "stock" && (
{stockPredictions && stockPredictions.predictions.length > 0 ? ( <>

{stockPredictions.totalProducts} products tracked

{stockPredictions.productsNeedingRestock > 0 && (

{stockPredictions.productsNeedingRestock} products need restocking soon

)}
Product Current Stock Days Until Out Estimated Date Confidence Status {stockPredictions.predictions.map((prediction) => ( {prediction.productName} {prediction.currentStock} {prediction.unitType} {prediction.prediction.daysUntilOutOfStock !== null ? (
{prediction.prediction.daysUntilOutOfStock} days
{prediction.prediction.optimisticDays !== null && prediction.prediction.pessimisticDays && (
{prediction.prediction.optimisticDays} -{" "} {prediction.prediction.pessimisticDays} days
)}
) : ( "N/A" )}
{prediction.prediction.estimatedDate ? (
{format( new Date( prediction.prediction.estimatedDate, ), "MMM d, yyyy", )}
{prediction.prediction.optimisticDate && prediction.prediction.pessimisticDate && (
{format( new Date( prediction.prediction.optimisticDate, ), "MMM d", )}{" "} -{" "} {format( new Date( prediction.prediction.pessimisticDate, ), "MMM d", )}
)}
) : ( "N/A" )}
{getConfidenceLabel(prediction.prediction.confidence)} {prediction.needsRestock ? ( Restock Soon ) : ( OK )}
))}
) : (

No stock predictions available

Enable stock tracking on products to get predictions

)}
)} ); }