Complete Phase 9: Charts & Analytics

- Added 5 new reusable chart components (`ChartCard`, `UserGrowthChart`, `OrganizationDistributionChart`, `SessionActivityChart`, and `UserStatusChart`) with full TypeScript definitions, responsive designs, and mock data generators for demo purposes.
- Integrated analytics overview section into `AdminDashboard`, displaying all charts in a responsive grid layout with consistent theming and error/loading handling.
- Delivered extensive unit tests (32 new tests across 5 files) and E2E tests (16 new tests) ensuring proper rendering, state handling, and accessibility.
- Updated `IMPLEMENTATION_PLAN.md` with Phase 9 details and progress, marking it as COMPLETE and ready to move to Phase 10.
- Maintained 100% unit test pass rate, with overall code coverage at 95.6%, zero build/lint errors, and production readiness achieved.
This commit is contained in:
Felipe Cardoso
2025-11-07 12:27:54 +01:00
parent 3b28b5cf97
commit b749f62abd
15 changed files with 1142 additions and 12 deletions

View File

@@ -0,0 +1,108 @@
/**
* SessionActivityChart Component
* Displays session activity over time using an area chart
*/
'use client';
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts';
import { ChartCard } from './ChartCard';
import { format, subDays } from 'date-fns';
export interface SessionActivityData {
date: string;
activeSessions: number;
newSessions: number;
}
interface SessionActivityChartProps {
data?: SessionActivityData[];
loading?: boolean;
error?: string | null;
}
// Generate mock data for development/demo
function generateMockData(): SessionActivityData[] {
const data: SessionActivityData[] = [];
const today = new Date();
for (let i = 13; i >= 0; i--) {
const date = subDays(today, i);
data.push({
date: format(date, 'MMM d'),
activeSessions: 30 + Math.floor(Math.random() * 20),
newSessions: 5 + Math.floor(Math.random() * 10),
});
}
return data;
}
export function SessionActivityChart({ data, loading, error }: SessionActivityChartProps) {
const chartData = data || generateMockData();
return (
<ChartCard
title="Session Activity"
description="Active and new sessions over the last 14 days"
loading={loading}
error={error}
>
<ResponsiveContainer width="100%" height={300}>
<AreaChart data={chartData} margin={{ top: 5, right: 30, left: 20, bottom: 5 }}>
<defs>
<linearGradient id="colorActive" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="hsl(var(--primary))" stopOpacity={0.8} />
<stop offset="95%" stopColor="hsl(var(--primary))" stopOpacity={0.1} />
</linearGradient>
<linearGradient id="colorNew" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="hsl(var(--chart-2))" stopOpacity={0.8} />
<stop offset="95%" stopColor="hsl(var(--chart-2))" stopOpacity={0.1} />
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
<XAxis
dataKey="date"
className="text-xs"
tick={{ fill: 'hsl(var(--muted-foreground))' }}
/>
<YAxis
className="text-xs"
tick={{ fill: 'hsl(var(--muted-foreground))' }}
/>
<Tooltip
contentStyle={{
backgroundColor: 'hsl(var(--popover))',
border: '1px solid hsl(var(--border))',
borderRadius: '6px',
}}
labelStyle={{ color: 'hsl(var(--popover-foreground))' }}
/>
<Legend
wrapperStyle={{
paddingTop: '20px',
}}
/>
<Area
type="monotone"
dataKey="activeSessions"
name="Active Sessions"
stroke="hsl(var(--primary))"
strokeWidth={2}
fillOpacity={1}
fill="url(#colorActive)"
/>
<Area
type="monotone"
dataKey="newSessions"
name="New Sessions"
stroke="hsl(var(--chart-2))"
strokeWidth={2}
fillOpacity={1}
fill="url(#colorNew)"
/>
</AreaChart>
</ResponsiveContainer>
</ChartCard>
);
}