Skip to content

Commit 604d164

Browse files
software321devYour Name
andauthored
feat(certificate): add certificate analytics dashboard for students (#642)
Co-authored-by: Your Name <you@example.com>
1 parent ece972a commit 604d164

1 file changed

Lines changed: 133 additions & 0 deletions

File tree

  • frontend/src/app/certificates/analytics
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
'use client';
2+
3+
import { useAuth } from '@/contexts/AuthContext';
4+
import { analyticsAPI } from '@/lib/api';
5+
import { useRouter } from 'next/navigation';
6+
import { useEffect, useState } from 'react';
7+
8+
export default function CertificateAnalyticsPage() {
9+
const { user } = useAuth();
10+
const router = useRouter();
11+
const [analytics, setAnalytics] = useState<any>(null);
12+
const [isLoading, setIsLoading] = useState(true);
13+
14+
useEffect(() => {
15+
if (!user) {
16+
router.push('/auth/login');
17+
return;
18+
}
19+
20+
async function loadAnalytics() {
21+
try {
22+
const data = await analyticsAPI.getUserAnalytics(user!.id);
23+
setAnalytics(data);
24+
} catch (error) {
25+
console.error('Failed to load analytics:', error);
26+
} finally {
27+
setIsLoading(false);
28+
}
29+
}
30+
31+
loadAnalytics();
32+
}, [user, router]);
33+
34+
if (isLoading) {
35+
return (
36+
<div className="flex min-h-[calc(100vh-80px)] items-center justify-center bg-black">
37+
<div className="text-center">
38+
<div className="mx-auto mb-4 h-16 w-16 animate-spin rounded-full border-4 border-red-600/30 border-t-red-600"></div>
39+
<p className="font-mono text-sm tracking-widest text-red-500 uppercase">
40+
Loading Analytics...
41+
</p>
42+
</div>
43+
</div>
44+
);
45+
}
46+
47+
if (!analytics) {
48+
return (
49+
<div className="flex min-h-[calc(100vh-80px)] items-center justify-center bg-black">
50+
<div className="text-center">
51+
<p className="font-mono text-sm tracking-widest text-red-500 uppercase">
52+
No analytics data available
53+
</p>
54+
</div>
55+
</div>
56+
);
57+
}
58+
59+
return (
60+
<div className="relative min-h-[calc(100vh-80px)] overflow-hidden bg-black p-8 text-white md:p-12">
61+
{/* Background Effect */}
62+
<div className="pointer-events-none absolute top-0 right-0 h-[500px] w-[500px] rounded-full bg-red-600/10 blur-[100px]"></div>
63+
64+
<div className="relative z-10 mx-auto max-w-7xl">
65+
<div className="mb-12 border-l-4 border-red-600 py-2 pl-6">
66+
<h1 className="mb-2 text-4xl font-black tracking-tight uppercase md:text-5xl">
67+
Certificate <span className="text-red-500">Analytics</span>
68+
</h1>
69+
<p className="font-mono text-lg text-sm font-light tracking-wide text-gray-400 uppercase">
70+
Performance metrics for Operator: <span className="text-white">{user?.name}</span>
71+
</p>
72+
</div>
73+
74+
<div className="grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-4">
75+
<div className="rounded-2xl border border-white/10 bg-zinc-950 p-6 shadow-xl">
76+
<h3 className="mb-4 text-sm font-bold tracking-widest text-white uppercase">
77+
Total Certificates
78+
</h3>
79+
<p className="text-3xl font-black text-white">{analytics.certificatesCount || 0}</p>
80+
</div>
81+
82+
<div className="rounded-2xl border border-white/10 bg-zinc-950 p-6 shadow-xl">
83+
<h3 className="mb-4 text-sm font-bold tracking-widest text-white uppercase">
84+
Active Certificates
85+
</h3>
86+
<p className="text-3xl font-black text-white">{analytics.certificates?.filter((c: any) => c.status === 'ACTIVE')?.length || 0}</p>
87+
</div>
88+
89+
<div className="rounded-2xl border border-white/10 bg-zinc-950 p-6 shadow-xl">
90+
<h3 className="mb-4 text-sm font-bold tracking-widest text-white uppercase">
91+
Revoked Certificates
92+
</h3>
93+
<p className="text-3xl font-black text-white">{analytics.certificates?.filter((c: any) => c.status === 'REVOKED')?.length || 0}</p>
94+
</div>
95+
96+
<div className="rounded-2xl border border-white/10 bg-zinc-950 p-6 shadow-xl">
97+
<h3 className="mb-4 text-sm font-bold tracking-widest text-white uppercase">
98+
Revocation Rate
99+
</h3>
100+
<p className="text-3xl font-black text-white">{analytics.certificates ? ((analytics.certificates.filter((c: any) => c.status === 'REVOKED').length / analytics.certificates.length) * 100).toFixed(1) + '%' : '0%'}</p>
101+
</div>
102+
</div>
103+
104+
<div className="mt-8 rounded-2xl border border-white/10 bg-zinc-950 p-6 shadow-xl">
105+
<h3 className="mb-6 border-b border-white/10 pb-4 text-sm font-bold tracking-widest text-white uppercase">
106+
Certificate Status Distribution
107+
</h3>
108+
109+
<div className="space-y-4">
110+
{analytics.certificates && analytics.certificates.length > 0 ? (
111+
Object.entries(
112+
analytics.certificates.reduce((acc: Record<string, number>, cert: any) => {
113+
acc[cert.status] = (acc[cert.status] || 0) + 1;
114+
return acc;
115+
}, {} as Record<string, number>)
116+
).map(([status, count]) => (
117+
<div key={status} className="flex items-center justify-between rounded border border-white/5 bg-black p-3">
118+
<span className="text-gray-500 capitalize">{status}</span>
119+
<span className="font-bold text-white">{count}</span>
120+
</div>
121+
))
122+
) : (
123+
<div className="flex items-center justify-between rounded border border-white/5 bg-black p-3">
124+
<span className="text-gray-500">No certificates</span>
125+
<span className="font-bold text-white">0</span>
126+
</div>
127+
)}
128+
</div>
129+
</div>
130+
</div>
131+
</div>
132+
);
133+
}

0 commit comments

Comments
 (0)