| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321 |
- 'use client'
- import { useEffect, useState } from 'react'
- import { MainLayout } from '@/components/layout/main-layout'
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
- import { BookOpen, Calendar, ClipboardList, TrendingUp, Clock, CheckCircle, XCircle, Loader2 } from 'lucide-react'
- import { toast } from 'sonner'
- interface Student {
- id: string;
- firstName: string;
- lastName: string;
- admissionNumber: string;
- }
- interface Stats {
- totalClasses: number;
- attendanceRate: number;
- todayClasses: number;
- monthlyAbsences: number;
- }
- interface TodayScheduleItem {
- id: string;
- name: string;
- section: string;
- time: string;
- room: string;
- professor: string;
- }
- interface AttendanceHistoryItem {
- id: string;
- class: string;
- date: string;
- status: string;
- time: string;
- statusColor: string;
- }
- interface ClassOverviewItem {
- id: string;
- name: string;
- section: string;
- professor: string;
- attendance: number;
- totalClasses: number;
- attendedClasses: number;
- }
- interface DashboardData {
- student: Student;
- stats: Stats;
- todaySchedule: TodayScheduleItem[];
- recentAttendances: AttendanceHistoryItem[];
- classesOverview: ClassOverviewItem[];
- }
- export default function StudentDashboard() {
- const [data, setData] = useState<DashboardData | null>(null)
- const [loading, setLoading] = useState(true)
- useEffect(() => {
- fetchDashboardData()
- }, [])
- const fetchDashboardData = async () => {
- try {
- setLoading(true)
- const response = await fetch('/api/student/dashboard')
- if (!response.ok) {
- throw new Error('Error al cargar los datos del dashboard')
- }
- const dashboardData: DashboardData = await response.json()
- setData(dashboardData)
- } catch (error) {
- console.error('Error:', error)
- toast.error('Error al cargar los datos del dashboard')
- } finally {
- setLoading(false)
- }
- }
- if (loading) {
- return (
- <MainLayout
- title="Dashboard Estudiante"
- subtitle="Mi progreso académico y asistencia"
- requiredRole="STUDENT"
- >
- <div className="flex items-center justify-center h-64">
- <Loader2 className="h-8 w-8 animate-spin" />
- </div>
- </MainLayout>
- )
- }
- if (!data) {
- return (
- <MainLayout
- title="Dashboard Estudiante"
- subtitle="Mi progreso académico y asistencia"
- requiredRole="STUDENT"
- >
- <div className="text-center py-8">
- <p className="text-muted-foreground">No se pudieron cargar los datos del dashboard.</p>
- </div>
- </MainLayout>
- )
- }
- const stats = [
- {
- title: 'Mis Clases',
- value: data.stats.totalClasses.toString(),
- description: 'Clases matriculadas',
- icon: BookOpen,
- color: 'text-blue-600',
- bgColor: 'bg-blue-100'
- },
- {
- title: 'Asistencia General',
- value: `${data.stats.attendanceRate}%`,
- description: 'Promedio del semestre',
- icon: TrendingUp,
- color: 'text-green-600',
- bgColor: 'bg-green-100'
- },
- {
- title: 'Clases Hoy',
- value: data.stats.todayClasses.toString(),
- description: 'Clases programadas',
- icon: Calendar,
- color: 'text-purple-600',
- bgColor: 'bg-purple-100'
- },
- {
- title: 'Faltas Este Mes',
- value: data.stats.monthlyAbsences.toString(),
- description: 'Inasistencias registradas',
- icon: XCircle,
- color: 'text-red-600',
- bgColor: 'bg-red-100'
- }
- ]
- return (
- <MainLayout
- title="Dashboard Estudiante"
- subtitle={`Bienvenido/a ${data.student.firstName} ${data.student.lastName}`}
- requiredRole="STUDENT"
- >
- <div className="space-y-6">
- {/* Estadísticas */}
- <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
- {stats.map((stat) => {
- const Icon = stat.icon
- return (
- <Card key={stat.title}>
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
- <CardTitle className="text-sm font-medium">
- {stat.title}
- </CardTitle>
- <div className={`p-2 rounded-full ${stat.bgColor}`}>
- <Icon className={`h-4 w-4 ${stat.color}`} />
- </div>
- </CardHeader>
- <CardContent>
- <div className="text-2xl font-bold">{stat.value}</div>
- <p className="text-xs text-muted-foreground">
- {stat.description}
- </p>
- </CardContent>
- </Card>
- )
- })}
- </div>
- <div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
- {/* Horario de Hoy */}
- <Card>
- <CardHeader>
- <CardTitle className="flex items-center gap-2">
- <Clock className="h-5 w-5" />
- Horario de Hoy
- </CardTitle>
- <CardDescription>
- Clases programadas para hoy
- </CardDescription>
- </CardHeader>
- <CardContent>
- <div className="space-y-4">
- {data.todaySchedule.length > 0 ? (
- data.todaySchedule.map((classItem) => (
- <div key={classItem.id} className="p-3 border rounded-lg">
- <div className="space-y-2">
- <p className="text-sm font-medium">{classItem.name}</p>
- <p className="text-xs text-muted-foreground">
- {classItem.section} • {classItem.room}
- </p>
- <p className="text-xs text-muted-foreground">
- {classItem.time} • Prof. {classItem.professor}
- </p>
- </div>
- </div>
- ))
- ) : (
- <p className="text-sm text-muted-foreground text-center py-4">
- No hay clases programadas para hoy
- </p>
- )}
- </div>
- </CardContent>
- </Card>
- {/* Historial de Asistencia */}
- <Card>
- <CardHeader>
- <CardTitle className="flex items-center gap-2">
- <ClipboardList className="h-5 w-5" />
- Asistencia Reciente
- </CardTitle>
- <CardDescription>
- Últimos registros de asistencia
- </CardDescription>
- </CardHeader>
- <CardContent>
- <div className="space-y-3">
- {data.recentAttendances.length > 0 ? (
- data.recentAttendances.map((record) => (
- <div key={record.id} className="flex items-center justify-between">
- <div className="space-y-1">
- <p className="text-sm font-medium">{record.class}</p>
- <p className="text-xs text-muted-foreground">
- {record.date} • {record.time}
- </p>
- </div>
- <div className="flex items-center gap-2">
- {record.status === 'Presente' || record.status === 'Justificado' ? (
- <CheckCircle className="h-4 w-4 text-green-600" />
- ) : (
- <XCircle className="h-4 w-4 text-red-600" />
- )}
- <span className={`text-xs font-medium ${record.statusColor}`}>
- {record.status}
- </span>
- </div>
- </div>
- ))
- ) : (
- <p className="text-sm text-muted-foreground text-center py-4">
- No hay registros de asistencia
- </p>
- )}
- </div>
- </CardContent>
- </Card>
- {/* Resumen por Clase */}
- <Card>
- <CardHeader>
- <CardTitle className="flex items-center gap-2">
- <BookOpen className="h-5 w-5" />
- Mis Clases
- </CardTitle>
- <CardDescription>
- Resumen de asistencia por materia
- </CardDescription>
- </CardHeader>
- <CardContent>
- <div className="space-y-4">
- {data.classesOverview.length > 0 ? (
- data.classesOverview.map((classItem) => (
- <div key={classItem.id} className="space-y-2">
- <div className="flex justify-between items-start">
- <div>
- <p className="text-sm font-medium">{classItem.name}</p>
- <p className="text-xs text-muted-foreground">
- {classItem.section} • Prof. {classItem.professor}
- </p>
- </div>
- <span className={`text-xs font-medium ${
- classItem.attendance >= 90
- ? 'text-green-600'
- : classItem.attendance >= 80
- ? 'text-yellow-600'
- : 'text-red-600'
- }`}>
- {classItem.attendance}%
- </span>
- </div>
- <div className="w-full bg-gray-200 rounded-full h-2">
- <div
- className={`h-2 rounded-full ${
- classItem.attendance >= 90
- ? 'bg-green-600'
- : classItem.attendance >= 80
- ? 'bg-yellow-600'
- : 'bg-red-600'
- }`}
- style={{ width: `${classItem.attendance}%` }}
- ></div>
- </div>
- <p className="text-xs text-muted-foreground">
- {classItem.attendedClasses}/{classItem.totalClasses} clases asistidas
- </p>
- </div>
- ))
- ) : (
- <p className="text-sm text-muted-foreground text-center py-4">
- No hay clases matriculadas
- </p>
- )}
- </div>
- </CardContent>
- </Card>
- </div>
- </div>
- </MainLayout>
- )
- }
|