From 38779c203309ae998a6ae631a450a88f9df77d74 Mon Sep 17 00:00:00 2001 From: g Date: Tue, 13 Jan 2026 08:01:09 +0000 Subject: [PATCH] Add AI-powered smart insights to admin analytics Introduces a getSmartInsights function to generate actionable AI-driven insights based on analytics and growth data. Displays up to four prioritized insights in the admin dashboard, including AI forecasts, revenue and order trends, customer acquisition, and loyalty metrics. Updates AnalyticsData interface to support new comparison and prediction fields. --- components/admin/AdminAnalytics.tsx | 218 +++++++++++++++++++++++++++- public/git-info.json | 4 +- 2 files changed, 219 insertions(+), 3 deletions(-) diff --git a/components/admin/AdminAnalytics.tsx b/components/admin/AdminAnalytics.tsx index b747158..dd1426e 100644 --- a/components/admin/AdminAnalytics.tsx +++ b/components/admin/AdminAnalytics.tsx @@ -29,6 +29,7 @@ import { Package, Trophy, PieChart as PieChartIcon, + Zap, } from "lucide-react"; import { Alert, AlertDescription, AlertTitle } from "@/components/common/alert"; import { fetchClient } from "@/lib/api/api-client"; @@ -46,7 +47,7 @@ import { AreaChart, Area, } from "recharts"; -import { formatGBP, formatNumber } from "@/lib/utils/format"; +import { formatGBP, formatNumber, formatCurrency } from "@/lib/utils/format"; import { PieChart, Pie, Cell, Legend, Sector } from "recharts"; import { AdminStatCard } from "./AdminStatCard"; import { TrendIndicator } from "./TrendIndicator"; @@ -132,6 +133,168 @@ const renderActiveShape = (props: any) => { ); }; +const getSmartInsights = (data?: AnalyticsData | null, growth?: GrowthData | null) => { + const insights: Array<{ + type: 'positive' | 'neutral' | 'information'; + message: string; + icon: any; + color: string; + bg: string; + }> = []; + + if (!data) return insights; + + // 1. AI Forecast (Always high priority if available) + if (data.predictions && data.predictions.predicted > 0) { + insights.push({ + type: 'positive', + message: `AI Forecast: Projected ${formatCurrency(data.predictions.predicted)} revenue over the next 7 days.`, + icon: Zap, + color: 'text-purple-500', + bg: 'bg-purple-500/10' + }); + } + + // 2. Comprehensive Comparisons Data + if (data.comparisons) { + const rawInsights: Array<{ + score: number; // Importance score + type: 'positive' | 'neutral' | 'information'; + message: string; + icon: any; + color: string; + bg: string; + }> = []; + + const periods = Object.entries(data.comparisons); + + periods.forEach(([label, metrics]) => { + const timeframeName = label === '1w' ? 'last week' : + label === '2w' ? 'last 2 weeks' : + label === '1m' ? 'last month' : + label === '3m' ? 'last 3 months' : + label === '6m' ? 'last 6 months' : + label === '1y' ? 'last year' : 'last 6 months'; + + // --- Revenue Insight --- + if (metrics.revenue.previous > 0) { + const revDiff = metrics.revenue.current - metrics.revenue.previous; + const revPercent = (revDiff / metrics.revenue.previous) * 100; + + if (Math.abs(revPercent) > 5) { + rawInsights.push({ + score: Math.abs(revPercent) * (label === '1w' ? 1.5 : 1), // Weight recent changes higher + type: revPercent > 0 ? 'positive' : 'neutral', + message: `Revenue is ${revPercent > 0 ? 'up' : 'down'} ${Math.abs(revPercent).toFixed(1)}% vs ${timeframeName} (${formatCurrency(Math.abs(revDiff))} difference).`, + icon: revPercent > 0 ? TrendingUp : TrendingDown, + color: revPercent > 0 ? 'text-green-500' : 'text-orange-500', + bg: revPercent > 0 ? 'bg-green-500/10' : 'bg-orange-500/10' + }); + } + } + + // --- Order Volume Insight --- + if (metrics.orders.previous > 0) { + const ordDiff = metrics.orders.current - metrics.orders.previous; + const ordPercent = (ordDiff / metrics.orders.previous) * 100; + + if (Math.abs(ordPercent) > 10) { + rawInsights.push({ + score: Math.abs(ordPercent) * 0.8, + type: ordPercent > 0 ? 'positive' : 'neutral', + message: `Order volume has ${ordPercent > 0 ? 'increased' : 'decreased'} by ${Math.abs(ordPercent).toFixed(0)}% compared to ${timeframeName}.`, + icon: ShoppingCart, + color: ordPercent > 0 ? 'text-blue-500' : 'text-slate-400', + bg: ordPercent > 0 ? 'bg-blue-500/10' : 'bg-slate-400/10' + }); + } + } + + // --- New Customer Insight --- + if (metrics.customers.previous > 0) { + const custDiff = metrics.customers.current - metrics.customers.previous; + const custPercent = (custDiff / metrics.customers.previous) * 100; + + if (custPercent > 5) { + rawInsights.push({ + score: custPercent * 1.2, + type: 'positive', + message: `New customer acquisition is up ${custPercent.toFixed(1)}% vs ${timeframeName}.`, + icon: Users, + color: 'text-emerald-500', + bg: 'bg-emerald-500/10' + }); + } + } + }); + + // Sort by importance (score) and pick top ones + rawInsights.sort((a, b) => b.score - a.score); + + // Add up to 3 most interesting comparison insights + rawInsights.slice(0, 3).forEach(ri => { + insights.push({ + type: ri.type, + message: ri.message, + icon: ri.icon, + color: ri.color, + bg: ri.bg + }); + }); + } + + // 3. Fallback / AI Trends if we have space + if (insights.length < 3 && data.predictions?.features?.trends) { + const { growthRate } = data.predictions.features.trends; + if (growthRate > 0.05) { + insights.push({ + type: 'positive', + message: `AI confirms a steady growth trend of ${(growthRate * 100).toFixed(1)}% in the current market.`, + icon: Zap, + color: 'text-purple-500', + bg: 'bg-purple-500/10' + }); + } + } + + // 4. Customer Loyalty Insight (from growth data) + if (insights.length < 4 && growth?.customers?.segments) { + const segments = growth.customers.segments; + const totalActive = segments.new + segments.returning + segments.loyal + segments.vip; + if (totalActive > 0) { + const returningRate = (segments.returning + segments.loyal + segments.vip) / totalActive; + if (returningRate > 0.4) { + insights.push({ + type: 'information', + message: `${(returningRate * 100).toFixed(0)}% of your audience consists of returning customers.`, + icon: Trophy, + color: 'text-amber-500', + bg: 'bg-amber-500/10' + }); + } + } + } + + // Ensure we have at least one message + if (insights.length === 0) { + insights.push({ + type: 'neutral', + message: 'Dashboard data calibrated. Marketplace performance is stable.', + icon: RefreshCw, + color: 'text-muted-foreground', + bg: 'bg-muted/10' + }); + } + + return insights.slice(0, 4); +}; + +interface ComparisonPeriod { + orders: { current: number; previous: number }; + revenue: { current: number; previous: number }; + customers: { current: number; previous: number }; +} + interface GrowthData { launchDate: string; generatedAt: string; @@ -213,6 +376,9 @@ interface AnalyticsData { total?: number; totalToday?: number; totalThisWeek?: number; + totalPreviousWeek?: number; + totalThisMonth?: number; + totalLastMonth?: number; pending?: number; completed?: number; dailyOrders?: { date: string; count: number }[]; @@ -221,6 +387,9 @@ interface AnalyticsData { total?: number; today?: number; thisWeek?: number; + previousWeek?: number; + thisMonth?: number; + lastMonth?: number; dailyRevenue?: { date: string; amount: number; @@ -246,6 +415,27 @@ interface AnalyticsData { total?: number; active?: number; }; + comparisons?: { + [key: string]: ComparisonPeriod; + }; + predictions?: { + predicted: number; + confidence: "high" | "medium" | "low"; + confidenceScore: number; + dailyPredictions: Array<{ date: string; predicted: number; confidence: string }>; + message?: string; + features?: { + trends: { + growthRate: number; + momentum: number; + volatility: number; + }; + seasonality: { + weekly: number[]; + monthly: number[]; + }; + }; + }; } export default function AdminAnalytics() { @@ -266,6 +456,8 @@ export default function AdminAnalytics() { const [growthData, setGrowthData] = useState(null); const [growthLoading, setGrowthLoading] = useState(false); + const insights = React.useMemo(() => getSmartInsights(analyticsData, growthData), [analyticsData, growthData]); + const [activeIndex, setActiveIndex] = useState(0); const onPieEnter = (_: any, index: number) => { @@ -865,6 +1057,30 @@ export default function AdminAnalytics() { /> + {/* AI Performance Insights */} + {insights.length > 0 && !loading && !refreshing && ( +
+ {insights.map((insight, index) => ( + + +
+ +
+
+

+ Insight + {insight.type === 'positive' && } +

+

+ {insight.message} +

+
+
+
+ ))} +
+ )} +