From a0605e47decef70833f3bb99bcfe388b561f2a1f Mon Sep 17 00:00:00 2001 From: g Date: Mon, 12 Jan 2026 04:52:40 +0000 Subject: [PATCH] Improve chart visuals and add null safety in analytics Refactored GrowthAnalyticsChart to use Area for 'orders' with gradient fill and improved dot handling. Enhanced PredictionsChart with consistent null checks for predictions data, improved tooltip rendering, and adjusted chart margins and axis styles. Updated RevenueChart to add activeDot styling for better interactivity. --- components/analytics/GrowthAnalyticsChart.tsx | 13 ++- components/analytics/PredictionsChart.tsx | 100 ++++++++++-------- components/analytics/RevenueChart.tsx | 1 + 3 files changed, 64 insertions(+), 50 deletions(-) diff --git a/components/analytics/GrowthAnalyticsChart.tsx b/components/analytics/GrowthAnalyticsChart.tsx index 4a30ccc..cde1ba3 100644 --- a/components/analytics/GrowthAnalyticsChart.tsx +++ b/components/analytics/GrowthAnalyticsChart.tsx @@ -201,6 +201,10 @@ export default function GrowthAnalyticsChart({ + + + + - diff --git a/components/analytics/PredictionsChart.tsx b/components/analytics/PredictionsChart.tsx index f749c7b..af37d4a 100644 --- a/components/analytics/PredictionsChart.tsx +++ b/components/analytics/PredictionsChart.tsx @@ -214,7 +214,7 @@ export default function PredictionsChart({ if (!predictions?.sales?.dailyPredictions) return []; const baselineData = baselinePredictions?.sales?.dailyPredictions || []; - const simulatedDailyData = predictions.sales.dailyPredictions; + const simulatedDailyData = predictions?.sales?.dailyPredictions || []; return simulatedDailyData.map((d: any, idx: number) => ({ ...d, @@ -307,7 +307,7 @@ export default function PredictionsChart({ Predictions & Forecasting - {predictions.sales.aiModel?.used + {predictions?.sales?.aiModel?.used ? "AI neural network + statistical models for sales, demand, and inventory" : "AI-powered predictions for sales, demand, and inventory"} @@ -372,13 +372,13 @@ export default function PredictionsChart({ - {predictions.sales.predicted !== null ? ( + {predictions?.sales?.predicted !== null && predictions?.sales?.predicted !== undefined ? (
- {getConfidenceLabel(predictions.sales.confidence)} Confidence - {predictions.sales.confidenceScore !== undefined && ( + {getConfidenceLabel(predictions?.sales?.confidence || "low")} Confidence + {predictions?.sales?.confidenceScore !== undefined && ( - ({Math.round(predictions.sales.confidenceScore * 100)}%) + ({Math.round((predictions?.sales?.confidenceScore || 0) * 100)}%) )} @@ -414,15 +414,15 @@ export default function PredictionsChart({ - {predictions.sales.aiModel?.used && ( + {predictions?.sales?.aiModel?.used && ( 🤖 AI Powered - {predictions.sales.aiModel.modelAccuracy !== undefined && ( + {predictions?.sales?.aiModel?.modelAccuracy !== undefined && ( - ({Math.round(predictions.sales.aiModel.modelAccuracy * 100)}%) + ({Math.round((predictions?.sales?.aiModel?.modelAccuracy || 0) * 100)}%) )} @@ -433,29 +433,29 @@ export default function PredictionsChart({ )} - {predictions.sales.trend && ( + {predictions?.sales?.trend && ( - {predictions.sales.trend.direction === "up" && ( + {predictions?.sales?.trend?.direction === "up" && ( )} - {predictions.sales.trend.direction === "down" && ( + {predictions?.sales?.trend?.direction === "down" && ( )} - {predictions.sales.trend.direction === "up" + {predictions?.sales?.trend?.direction === "up" ? "Trending Up" - : predictions.sales.trend.direction === "down" + : predictions?.sales?.trend?.direction === "down" ? "Trending Down" : "Stable"} @@ -470,24 +470,24 @@ export default function PredictionsChart({ Next {daysAhead} days
- {predictions.sales.predictedOrders && ( + {predictions?.sales?.predictedOrders && (
- ~{Math.round(predictions.sales.predictedOrders)}{" "} + ~{Math.round(predictions?.sales?.predictedOrders || 0)}{" "} orders
)} - {!predictions.sales.confidenceIntervals && - predictions.sales.minPrediction && - predictions.sales.maxPrediction && ( + {!predictions?.sales?.confidenceIntervals && + predictions?.sales?.minPrediction && + predictions?.sales?.maxPrediction && (
- Range: {formatGBP(predictions.sales.minPrediction)} -{" "} - {formatGBP(predictions.sales.maxPrediction)} + Range: {formatGBP(predictions?.sales?.minPrediction || 0)} -{" "} + {formatGBP(predictions?.sales?.maxPrediction || 0)}
)}
) : (
- {predictions.sales.message || + {predictions?.sales?.message || "Insufficient data for prediction"}
)} @@ -559,8 +559,8 @@ export default function PredictionsChart({ {/* Daily Predictions Chart */} - {predictions.sales.dailyPredictions && - predictions.sales.dailyPredictions.length > 0 && ( + {predictions?.sales?.dailyPredictions && + predictions?.sales?.dailyPredictions.length > 0 && ( @@ -606,7 +606,7 @@ export default function PredictionsChart({ -
+
{isSimulating && (
@@ -615,12 +615,7 @@ export default function PredictionsChart({ @@ -651,28 +646,37 @@ export default function PredictionsChart({ `£${value}`} /> { + if (active && payload?.length) { + const data = payload[0].payload; + return ( +
+

{data.formattedDate}

+

+ Baseline: {formatGBP(data.baseline)} +

+ {committedSimulationFactor !== 0 && ( +

+ Simulated: {formatGBP(data.simulated)} +

+ )} +
+ ); + } + return null; }} - formatter={(value: number, name: string) => [ - formatGBP(value), - name === "baseline" ? "Baseline" : "Simulated" - ]} /> {/* Always show baseline as solid line */} {/* Show simulated line when simulation is active */} {committedSimulationFactor !== 0 && ( @@ -693,6 +698,7 @@ export default function PredictionsChart({ fill="url(#colorSimulated)" strokeDasharray="5 5" strokeWidth={2} + activeDot={{ r: 5, strokeWidth: 0 }} /> )}
diff --git a/components/analytics/RevenueChart.tsx b/components/analytics/RevenueChart.tsx index a272606..c941b82 100644 --- a/components/analytics/RevenueChart.tsx +++ b/components/analytics/RevenueChart.tsx @@ -208,6 +208,7 @@ export default function RevenueChart({ timeRange, hideNumbers = false }: Revenue fillOpacity={1} fill="url(#colorRevenue)" strokeWidth={2} + activeDot={{ r: 4, strokeWidth: 0 }} />