247 lines
9.8 KiB
TypeScript

/* eslint-disable @typescript-eslint/no-explicit-any */
import { createClient } from '@/lib/supabase/server'
import {
Monitor,
Users,
TrendingUp,
CheckCircle,
Zap,
ArrowRight,
FileText,
} from 'lucide-react'
import Link from 'next/link'
import { cn } from '@/lib/utils'
import NotificationBell from '@/components/dashboard/NotificationBell'
function KpiCard({
label,
value,
badge,
badgePositive,
icon: Icon,
}: {
label: string
value: string
badge: string
badgePositive: boolean
icon: React.ElementType
}) {
return (
<div className="card p-4 md:p-5 flex-1">
<div className="flex items-center justify-between mb-3">
<span className="text-sm text-text-secondary">{label}</span>
<div className="text-text-muted"><Icon size={18} /></div>
</div>
<div className="flex items-end gap-2">
<span className="text-3xl font-bold text-text-primary">{value}</span>
<span className={cn(
'text-xs font-medium px-2 py-0.5 rounded-full mb-1',
badgePositive ? 'bg-green-500/10 text-green-400' : 'bg-red-500/10 text-red-400'
)}>
{badge}
</span>
</div>
</div>
)
}
const statusConfig = {
active: { label: 'ACTIVE NOW', color: 'bg-green-500/10 text-green-400 border-green-500/20' },
scheduled: { label: 'PLANIFIÉE', color: 'bg-amber-500/10 text-amber-400 border-amber-500/20' },
completed: { label: 'TERMINÉE', color: 'bg-gray-500/10 text-gray-400 border-gray-500/20' },
} as const
function SessionCard({ session }: { session: any }) {
const statusKey: keyof typeof statusConfig = session.status
const status = statusConfig[statusKey] ?? statusConfig.completed
return (
<div className="card p-3 md:p-4 flex items-center gap-3 md:gap-4 hover:border-border-light transition-colors">
<div className="hidden sm:block w-16 h-14 bg-background-elevated rounded-lg flex-shrink-0 overflow-hidden">
<div className="w-full h-full bg-gradient-to-br from-primary/30 to-blue-700/20 flex items-center justify-center">
<FileText size={20} className="text-primary/60" />
</div>
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<h3 className="font-semibold text-text-primary truncate">{session.title}</h3>
<span className={cn('text-xs px-2 py-0.5 rounded-full border font-medium flex-shrink-0', status.color)}>
{status.label}
</span>
</div>
<p className="text-xs text-text-muted mb-2">Code: #{session.shortCode} · {session.createdAt}</p>
<div className="flex items-center gap-4 text-xs text-text-secondary">
<span className="flex items-center gap-1"><Users size={12} />{session.participants} Participants</span>
{session.status === 'active' && (
<div className="flex items-center gap-1.5 flex-1">
<div className="flex-1 h-1.5 bg-border rounded-full overflow-hidden">
<div className="h-full bg-primary rounded-full" style={{ width: `${session.completion}%` }} />
</div>
<span>{session.completion}% Complete</span>
</div>
)}
{session.status === 'completed' && session.avgScore && (
<span className="flex items-center gap-1 text-green-400">
<TrendingUp size={12} />{session.avgScore}% Avg Score
</span>
)}
</div>
</div>
<div className="flex flex-col gap-2 flex-shrink-0">
{session.status === 'active' && (
<>
<Link href={`/dashboard/sessions/${session.id}/live`} className="btn-primary text-xs px-3 py-1.5">
<Monitor size={12} />Monitor
</Link>
<button className="btn-secondary text-xs px-3 py-1.5">Détails</button>
</>
)}
{session.status === 'completed' && (
<Link href={`/dashboard/sessions/${session.id}/live`} className="btn-secondary text-xs px-3 py-1.5">
Voir Rapport
</Link>
)}
</div>
</div>
)
}
export default async function DashboardPage() {
const supabase = await createClient()
const db = supabase as any
const { data: { user } } = await supabase.auth.getUser()
const { data: profile } = await db
.from('profiles')
.select('username')
.eq('id', user!.id)
.single()
const { data: sessions } = await db
.from('sessions')
.select('id, short_code, is_active, created_at, school_name, class_name, total_participants, quiz:quizzes(title)')
.eq('trainer_id', user!.id)
.order('created_at', { ascending: false })
.limit(5)
const { count: totalSessions } = await db
.from('sessions')
.select('id', { count: 'exact', head: true })
.eq('trainer_id', user!.id)
const activeSessionIds = (sessions ?? [])
.filter((s: any) => s.is_active)
.map((s: any) => s.id)
const { count: totalParticipants } = activeSessionIds.length > 0
? await db
.from('student_participations')
.select('id', { count: 'exact', head: true })
.in('session_id', activeSessionIds)
.eq('status', 'in_progress')
: { count: 0 }
const displayName = profile?.username ?? user?.email?.split('@')[0] ?? 'Formateur'
const mappedSessions = (sessions ?? []).map((s: any) => ({
id: s.id,
title: s.quiz?.title ?? 'Quiz sans titre',
shortCode: s.short_code,
createdAt: s.is_active
? `il y a ${Math.max(0, Math.floor((Date.now() - new Date(s.created_at).getTime()) / 3600000))}h`
: 'Terminé',
participants: s.total_participants,
avgTime: '15m',
completion: 85,
status: s.is_active ? 'active' as const : 'completed' as const,
}))
return (
<div className="p-4 md:p-8">
<div className="flex items-start justify-between mb-6 md:mb-8">
<div>
<h1 className="text-2xl md:text-3xl font-bold text-text-primary mb-1">Dashboard Overview</h1>
<p className="text-text-secondary text-sm md:text-base">Bienvenue, {displayName}. Voici ce qui se passe aujourd&apos;hui.</p>
</div>
<div className="flex items-center gap-3">
<NotificationBell />
<div className="w-9 h-9 bg-primary rounded-full flex items-center justify-center text-white font-semibold text-sm">
{displayName.slice(0, 2).toUpperCase()}
</div>
</div>
</div>
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3 md:gap-4 mb-6 md:mb-8">
<KpiCard label="Total Sessions" value={String(totalSessions ?? 0)} badge="+2 cette semaine" badgePositive icon={Monitor} />
<KpiCard label="Étudiants en cours" value={String(totalParticipants ?? 0)} badge="sessions actives" badgePositive icon={Users} />
<KpiCard label="Score Moyen" value="78%" badge="+5%" badgePositive icon={TrendingUp} />
<KpiCard label="Taux de Complétion" value="92%" badge="-1%" badgePositive={false} icon={CheckCircle} />
</div>
<div className="mb-8">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold text-text-primary">Sessions Actives</h2>
<Link href="/dashboard/reports" className="text-sm text-primary hover:text-primary-light transition-colors flex items-center gap-1">
Voir tout <ArrowRight size={14} />
</Link>
</div>
<div className="space-y-3">
{mappedSessions.length > 0 ? (
mappedSessions.map((session: any) => <SessionCard key={session.id} session={session} />)
) : (
<div className="card p-8 text-center">
<Monitor size={32} className="mx-auto text-text-muted mb-3" />
<p className="text-text-secondary mb-4">Aucune session créée pour l&apos;instant</p>
<Link href="/dashboard/sessions/create" className="btn-primary inline-flex">
Créer une session
</Link>
</div>
)}
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-6">
<div className="card p-6">
<h3 className="font-semibold text-text-primary mb-4">Notifications Récentes</h3>
<div className="space-y-4">
{[
{ icon: '🔧', title: 'Maintenance Système', desc: 'Prévue samedi à 22h. Sauvegardez votre travail.' },
{ icon: '✅', title: 'Session finalisée', desc: 'Les rapports sont disponibles pour JS Fundamentals.' },
{ icon: '👥', title: 'Nouvelles inscriptions', desc: '12 étudiants ont rejoint "React Basics Quiz".' },
].map((notif, i) => (
<div key={i} className="flex items-start gap-3">
<div className="w-8 h-8 bg-background-elevated rounded-lg flex items-center justify-center text-sm flex-shrink-0">
{notif.icon}
</div>
<div>
<p className="text-sm font-medium text-text-primary">{notif.title}</p>
<p className="text-xs text-text-muted">{notif.desc}</p>
</div>
</div>
))}
</div>
</div>
<div className="card p-6 bg-gradient-to-br from-primary to-blue-700 border-0">
<div className="w-10 h-10 bg-white/20 rounded-xl flex items-center justify-center mb-4">
<Zap size={20} className="text-white" />
</div>
<h3 className="font-bold text-white text-lg mb-2">Lancer un Quiz Rapide</h3>
<p className="text-blue-100 text-sm mb-6">
Créez instantanément un quiz pour un feedback immédiat de votre classe.
</p>
<Link
href="/dashboard/sessions/create"
className="inline-flex items-center gap-2 bg-white text-primary font-semibold px-4 py-2 rounded-lg hover:bg-blue-50 transition-colors"
>
Démarrer
</Link>
</div>
</div>
</div>
)
}