Add AI-powered smart insights to admin analytics
All checks were successful
Build Frontend / build (push) Successful in 1m14s

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.
This commit is contained in:
g
2026-01-13 08:01:09 +00:00
parent 600ba1e10e
commit 38779c2033
2 changed files with 219 additions and 3 deletions

View File

@@ -29,6 +29,7 @@ import {
Package, Package,
Trophy, Trophy,
PieChart as PieChartIcon, PieChart as PieChartIcon,
Zap,
} from "lucide-react"; } from "lucide-react";
import { Alert, AlertDescription, AlertTitle } from "@/components/common/alert"; import { Alert, AlertDescription, AlertTitle } from "@/components/common/alert";
import { fetchClient } from "@/lib/api/api-client"; import { fetchClient } from "@/lib/api/api-client";
@@ -46,7 +47,7 @@ import {
AreaChart, AreaChart,
Area, Area,
} from "recharts"; } 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 { PieChart, Pie, Cell, Legend, Sector } from "recharts";
import { AdminStatCard } from "./AdminStatCard"; import { AdminStatCard } from "./AdminStatCard";
import { TrendIndicator } from "./TrendIndicator"; 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 { interface GrowthData {
launchDate: string; launchDate: string;
generatedAt: string; generatedAt: string;
@@ -213,6 +376,9 @@ interface AnalyticsData {
total?: number; total?: number;
totalToday?: number; totalToday?: number;
totalThisWeek?: number; totalThisWeek?: number;
totalPreviousWeek?: number;
totalThisMonth?: number;
totalLastMonth?: number;
pending?: number; pending?: number;
completed?: number; completed?: number;
dailyOrders?: { date: string; count: number }[]; dailyOrders?: { date: string; count: number }[];
@@ -221,6 +387,9 @@ interface AnalyticsData {
total?: number; total?: number;
today?: number; today?: number;
thisWeek?: number; thisWeek?: number;
previousWeek?: number;
thisMonth?: number;
lastMonth?: number;
dailyRevenue?: { dailyRevenue?: {
date: string; date: string;
amount: number; amount: number;
@@ -246,6 +415,27 @@ interface AnalyticsData {
total?: number; total?: number;
active?: 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() { export default function AdminAnalytics() {
@@ -266,6 +456,8 @@ export default function AdminAnalytics() {
const [growthData, setGrowthData] = useState<GrowthData | null>(null); const [growthData, setGrowthData] = useState<GrowthData | null>(null);
const [growthLoading, setGrowthLoading] = useState(false); const [growthLoading, setGrowthLoading] = useState(false);
const insights = React.useMemo(() => getSmartInsights(analyticsData, growthData), [analyticsData, growthData]);
const [activeIndex, setActiveIndex] = useState(0); const [activeIndex, setActiveIndex] = useState(0);
const onPieEnter = (_: any, index: number) => { const onPieEnter = (_: any, index: number) => {
@@ -865,6 +1057,30 @@ export default function AdminAnalytics() {
/> />
</div> </div>
{/* AI Performance Insights */}
{insights.length > 0 && !loading && !refreshing && (
<div className="mt-8 grid grid-cols-1 md:grid-cols-3 gap-4">
{insights.map((insight, index) => (
<Card key={index} className="border-border/40 bg-background/50 backdrop-blur-sm shadow-sm hover:bg-background/80 transition-all duration-300">
<CardContent className="p-4 flex items-start gap-4">
<div className={`p-2 rounded-lg ${insight.bg} shrink-0`}>
<insight.icon className={`h-5 w-5 ${insight.color}`} />
</div>
<div>
<h4 className="font-semibold text-sm mb-1 flex items-center gap-2">
Insight
{insight.type === 'positive' && <span className="flex h-2 w-2 rounded-full bg-green-500 animate-pulse" />}
</h4>
<p className="text-sm text-muted-foreground leading-snug">
{insight.message}
</p>
</div>
</CardContent>
</Card>
))}
</div>
)}
<Tabs defaultValue="orders" className="mt-8"> <Tabs defaultValue="orders" className="mt-8">
<TabsList className="bg-background/40 backdrop-blur-md border border-border/40 p-1 w-full sm:w-auto h-auto grid grid-cols-3 sm:flex"> <TabsList className="bg-background/40 backdrop-blur-md border border-border/40 p-1 w-full sm:w-auto h-auto grid grid-cols-3 sm:flex">
<TabsTrigger <TabsTrigger

View File

@@ -1,4 +1,4 @@
{ {
"commitHash": "a07ca55", "commitHash": "600ba1e",
"buildTime": "2026-01-13T05:50:49.436Z" "buildTime": "2026-01-13T06:14:18.909Z"
} }