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 */}
{data.name}
- Count: {data.value.toLocaleString()} -
- Revenue:{" "} - {formatCurrency(details.totalRevenue)} -
- Avg Orders: {details.avgOrderCount} -