From 600ba1e10e0e608139753e725db813be51419a1c Mon Sep 17 00:00:00 2001 From: g Date: Tue, 13 Jan 2026 06:05:52 +0000 Subject: [PATCH] Enhance analytics charts with interactivity and skeletons Added interactive active segment highlighting to the customer segments pie chart and improved the monthly revenue/orders chart with gradient areas and labeled axes. Replaced loading spinners with ChartSkeleton components for a more consistent loading state. Refactored SkeletonLoaders to accept className and improved code style. --- components/admin/AdminAnalytics.tsx | 179 ++++++++++++++++------- components/analytics/SkeletonLoaders.tsx | 52 ++++--- 2 files changed, 153 insertions(+), 78 deletions(-) diff --git a/components/admin/AdminAnalytics.tsx b/components/admin/AdminAnalytics.tsx index 7965d14..b747158 100644 --- a/components/admin/AdminAnalytics.tsx +++ b/components/admin/AdminAnalytics.tsx @@ -28,6 +28,7 @@ import { DollarSign, Package, Trophy, + PieChart as PieChartIcon, } from "lucide-react"; import { Alert, AlertDescription, AlertTitle } from "@/components/common/alert"; import { fetchClient } from "@/lib/api/api-client"; @@ -46,7 +47,7 @@ import { Area, } from "recharts"; import { formatGBP, formatNumber } from "@/lib/utils/format"; -import { PieChart, Pie, Cell, Legend } from "recharts"; +import { PieChart, Pie, Cell, Legend, Sector } from "recharts"; import { AdminStatCard } from "./AdminStatCard"; import { TrendIndicator } from "./TrendIndicator"; import { @@ -56,6 +57,81 @@ import { TableSkeleton } from "../analytics/SkeletonLoaders"; +const renderActiveShape = (props: any) => { + const RADIAN = Math.PI / 180; + const { + cx, + cy, + midAngle, + innerRadius, + outerRadius, + startAngle, + endAngle, + fill, + payload, + percent, + value, + } = props; + const sin = Math.sin(-RADIAN * midAngle); + const cos = Math.cos(-RADIAN * midAngle); + const sx = cx + (outerRadius + 10) * cos; + const sy = cy + (outerRadius + 10) * sin; + const mx = cx + (outerRadius + 30) * cos; + const my = cy + (outerRadius + 30) * sin; + const ex = mx + (cos >= 0 ? 1 : -1) * 22; + const ey = my; + const textAnchor = cos >= 0 ? "start" : "end"; + + return ( + + + {payload.name.split(" ")[0]} + + + + + + = 0 ? 1 : -1) * 12} + y={ey} + textAnchor={textAnchor} + fill="#888" + fontSize={12} + >{`Count ${value}`} + = 0 ? 1 : -1) * 12} + y={ey} + dy={18} + textAnchor={textAnchor} + fill="#999" + fontSize={10} + > + {`(${(percent * 100).toFixed(0)}%)`} + + + ); +}; + interface GrowthData { launchDate: string; generatedAt: string; @@ -190,6 +266,12 @@ export default function AdminAnalytics() { const [growthData, setGrowthData] = useState(null); const [growthLoading, setGrowthLoading] = useState(false); + const [activeIndex, setActiveIndex] = useState(0); + + const onPieEnter = (_: any, index: number) => { + setActiveIndex(index); + }; + const currentYear = new Date().getFullYear(); // Segment colors for pie chart @@ -1180,9 +1262,13 @@ export default function AdminAnalytics() { {growthLoading ? ( -
-
-
+ ) : growthData?.monthly && growthData.monthly.length > 0 ? (
@@ -1198,9 +1284,19 @@ export default function AdminAnalytics() { }))} margin={{ top: 5, right: 30, left: 20, bottom: 5 }} > + + + + + + + + + + - + `£${(value / 1000).toFixed(0)}k` } + label={{ value: "Revenue", angle: 90, position: "insideRight", style: { fill: 'hsl(var(--muted-foreground))' } }} /> { if (active && payload?.length) { const data = payload[0].payload; @@ -1250,24 +1347,25 @@ export default function AdminAnalytics() { return null; }} /> - - @@ -1288,15 +1386,21 @@ export default function AdminAnalytics() { {growthLoading ? ( -
-
-
+ ) : growthData?.customers ? ( <>
`${(percent * 100).toFixed(0)}%`} - labelLine={false} - stroke="none" + onMouseEnter={onPieEnter} > {[ { color: SEGMENT_COLORS.new }, @@ -1338,37 +1440,6 @@ export default function AdminAnalytics() { ))} - { - if (active && payload?.length) { - const data = payload[0].payload; - const details = - growthData.customers.segmentDetails[ - data.name.split(" ")[0].toLowerCase() - ]; - return ( -
-

{data.name}

-

- Count: {data.value.toLocaleString()} -

- {details && ( - <> -

- Revenue:{" "} - {formatCurrency(details.totalRevenue)} -

-

- Avg Orders: {details.avgOrderCount} -

- - )} -
- ); - } - return null; - }} - />
diff --git a/components/analytics/SkeletonLoaders.tsx b/components/analytics/SkeletonLoaders.tsx index 3ff81ff..0c8b695 100644 --- a/components/analytics/SkeletonLoaders.tsx +++ b/components/analytics/SkeletonLoaders.tsx @@ -1,20 +1,24 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/common/card"; import { Skeleton } from "@/components/common/skeleton"; +import { cn } from "@/lib/utils"; + // Chart skeleton for revenue trends and order analytics -export function ChartSkeleton({ - title, - description, +export function ChartSkeleton({ + title, + description, icon: Icon, - showStats = false -}: { - title: string; - description: string; + showStats = false, + className, +}: { + title: string; + description: string; icon: any; showStats?: boolean; + className?: string; }) { return ( - + @@ -26,7 +30,7 @@ export function ChartSkeleton({
{/* Chart area */}
- + {/* Summary stats if applicable */} {showStats && (
@@ -45,15 +49,15 @@ export function ChartSkeleton({ } // Table skeleton for product performance -export function TableSkeleton({ - title, - description, +export function TableSkeleton({ + title, + description, icon: Icon, rows = 5, columns = 5 -}: { - title: string; - description: string; +}: { + title: string; + description: string; icon: any; rows?: number; columns?: number; @@ -75,7 +79,7 @@ export function TableSkeleton({ ))}
- + {/* Table rows */} {[...Array(rows)].map((_, rowIndex) => (
@@ -96,13 +100,13 @@ export function TableSkeleton({ } // Customer insights skeleton with segments -export function CustomerInsightsSkeleton({ - title, - description, - icon: Icon -}: { - title: string; - description: string; +export function CustomerInsightsSkeleton({ + title, + description, + icon: Icon +}: { + title: string; + description: string; icon: any; }) { return ( @@ -125,7 +129,7 @@ export function CustomerInsightsSkeleton({
))}
- + {/* Top customers table */}