|
|
@@ -0,0 +1,256 @@
|
|
|
+'use client'
|
|
|
+
|
|
|
+import { useState, useEffect } from 'react'
|
|
|
+import { DashboardLayout } from '@/components/dashboard-layout'
|
|
|
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
|
+import { Badge } from '@/components/ui/badge'
|
|
|
+import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from '@/components/ui/breadcrumb'
|
|
|
+import { BookOpen, Calendar, Users, CheckCircle, XCircle, Clock } from 'lucide-react'
|
|
|
+import { toast } from 'sonner'
|
|
|
+
|
|
|
+interface Section {
|
|
|
+ id: string
|
|
|
+ name: string
|
|
|
+ className: string
|
|
|
+ classCode: string
|
|
|
+ credits: number
|
|
|
+ teacherName: string
|
|
|
+ isActive: boolean
|
|
|
+}
|
|
|
+
|
|
|
+interface AttendanceSummary {
|
|
|
+ totalClasses: number
|
|
|
+ present: number
|
|
|
+ absent: number
|
|
|
+ tardy: number
|
|
|
+ attendanceRate: number
|
|
|
+}
|
|
|
+
|
|
|
+export default function StudentDashboard() {
|
|
|
+ const [sections, setSections] = useState<Section[]>([])
|
|
|
+ const [attendanceSummary, setAttendanceSummary] = useState<AttendanceSummary | null>(null)
|
|
|
+ const [loading, setLoading] = useState(true)
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ fetchStudentData()
|
|
|
+ }, [])
|
|
|
+
|
|
|
+ const fetchStudentData = async () => {
|
|
|
+ try {
|
|
|
+ // Simular datos por ahora - en el futuro se conectará a APIs reales
|
|
|
+ const mockSections: Section[] = [
|
|
|
+ {
|
|
|
+ id: '1',
|
|
|
+ name: 'Sección A',
|
|
|
+ className: 'Matemáticas Avanzadas',
|
|
|
+ classCode: 'MAT-401',
|
|
|
+ credits: 4,
|
|
|
+ teacherName: 'Prof. García',
|
|
|
+ isActive: true
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: '2',
|
|
|
+ name: 'Sección B',
|
|
|
+ className: 'Programación Web',
|
|
|
+ classCode: 'INF-301',
|
|
|
+ credits: 3,
|
|
|
+ teacherName: 'Prof. Rodríguez',
|
|
|
+ isActive: true
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: '3',
|
|
|
+ name: 'Sección C',
|
|
|
+ className: 'Base de Datos',
|
|
|
+ classCode: 'INF-302',
|
|
|
+ credits: 3,
|
|
|
+ teacherName: 'Prof. Martínez',
|
|
|
+ isActive: true
|
|
|
+ }
|
|
|
+ ]
|
|
|
+
|
|
|
+ const mockAttendance: AttendanceSummary = {
|
|
|
+ totalClasses: 45,
|
|
|
+ present: 38,
|
|
|
+ absent: 4,
|
|
|
+ tardy: 3,
|
|
|
+ attendanceRate: 84.4
|
|
|
+ }
|
|
|
+
|
|
|
+ setSections(mockSections)
|
|
|
+ setAttendanceSummary(mockAttendance)
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error:', error)
|
|
|
+ toast.error('Error al cargar los datos')
|
|
|
+ } finally {
|
|
|
+ setLoading(false)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const getAttendanceColor = (rate: number) => {
|
|
|
+ if (rate >= 90) return 'text-green-600'
|
|
|
+ if (rate >= 80) return 'text-yellow-600'
|
|
|
+ return 'text-red-600'
|
|
|
+ }
|
|
|
+
|
|
|
+ const getAttendanceBadgeVariant = (rate: number) => {
|
|
|
+ if (rate >= 90) return 'default'
|
|
|
+ if (rate >= 80) return 'secondary'
|
|
|
+ return 'destructive'
|
|
|
+ }
|
|
|
+
|
|
|
+ if (loading) {
|
|
|
+ return (
|
|
|
+ <DashboardLayout>
|
|
|
+ <div className="flex items-center justify-center min-h-[400px]">
|
|
|
+ <div className="text-center">
|
|
|
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
|
|
|
+ <p className="text-muted-foreground">Cargando...</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </DashboardLayout>
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <DashboardLayout>
|
|
|
+ <div className="space-y-6">
|
|
|
+ {/* Breadcrumb */}
|
|
|
+ <Breadcrumb>
|
|
|
+ <BreadcrumbList>
|
|
|
+ <BreadcrumbItem>
|
|
|
+ <BreadcrumbLink href="/student">Estudiante</BreadcrumbLink>
|
|
|
+ </BreadcrumbItem>
|
|
|
+ <BreadcrumbSeparator />
|
|
|
+ <BreadcrumbItem>
|
|
|
+ <BreadcrumbPage>Dashboard</BreadcrumbPage>
|
|
|
+ </BreadcrumbItem>
|
|
|
+ </BreadcrumbList>
|
|
|
+ </Breadcrumb>
|
|
|
+
|
|
|
+ {/* Header */}
|
|
|
+ <div className="flex items-center gap-3">
|
|
|
+ <div className="p-2 bg-primary/10 rounded-lg">
|
|
|
+ <BookOpen className="h-6 w-6 text-primary" />
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <h1 className="text-2xl font-bold">Mi Dashboard</h1>
|
|
|
+ <p className="text-muted-foreground">Resumen de tus clases y asistencia</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* Resumen de Asistencia */}
|
|
|
+ {attendanceSummary && (
|
|
|
+ <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
|
+ <Card>
|
|
|
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
|
+ <CardTitle className="text-sm font-medium">Total Clases</CardTitle>
|
|
|
+ <Calendar className="h-4 w-4 text-muted-foreground" />
|
|
|
+ </CardHeader>
|
|
|
+ <CardContent>
|
|
|
+ <div className="text-2xl font-bold">{attendanceSummary.totalClasses}</div>
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+
|
|
|
+ <Card>
|
|
|
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
|
+ <CardTitle className="text-sm font-medium">Presente</CardTitle>
|
|
|
+ <CheckCircle className="h-4 w-4 text-green-600" />
|
|
|
+ </CardHeader>
|
|
|
+ <CardContent>
|
|
|
+ <div className="text-2xl font-bold text-green-600">{attendanceSummary.present}</div>
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+
|
|
|
+ <Card>
|
|
|
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
|
+ <CardTitle className="text-sm font-medium">Ausente</CardTitle>
|
|
|
+ <XCircle className="h-4 w-4 text-red-600" />
|
|
|
+ </CardHeader>
|
|
|
+ <CardContent>
|
|
|
+ <div className="text-2xl font-bold text-red-600">{attendanceSummary.absent}</div>
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+
|
|
|
+ <Card>
|
|
|
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
|
+ <CardTitle className="text-sm font-medium">Tardanza</CardTitle>
|
|
|
+ <Clock className="h-4 w-4 text-yellow-600" />
|
|
|
+ </CardHeader>
|
|
|
+ <CardContent>
|
|
|
+ <div className="text-2xl font-bold text-yellow-600">{attendanceSummary.tardy}</div>
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {/* Porcentaje de Asistencia */}
|
|
|
+ {attendanceSummary && (
|
|
|
+ <Card>
|
|
|
+ <CardHeader>
|
|
|
+ <CardTitle className="flex items-center gap-2">
|
|
|
+ <CheckCircle className="h-5 w-5" />
|
|
|
+ Porcentaje de Asistencia
|
|
|
+ </CardTitle>
|
|
|
+ </CardHeader>
|
|
|
+ <CardContent>
|
|
|
+ <div className="flex items-center justify-between">
|
|
|
+ <div>
|
|
|
+ <div className={`text-3xl font-bold ${getAttendanceColor(attendanceSummary.attendanceRate)}`}>
|
|
|
+ {attendanceSummary.attendanceRate}%
|
|
|
+ </div>
|
|
|
+ <p className="text-sm text-muted-foreground mt-1">
|
|
|
+ {attendanceSummary.present} de {attendanceSummary.totalClasses} clases
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ <Badge variant={getAttendanceBadgeVariant(attendanceSummary.attendanceRate)}>
|
|
|
+ {attendanceSummary.attendanceRate >= 90 ? 'Excelente' :
|
|
|
+ attendanceSummary.attendanceRate >= 80 ? 'Bueno' : 'Necesita Mejorar'}
|
|
|
+ </Badge>
|
|
|
+ </div>
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {/* Mis Secciones */}
|
|
|
+ <Card>
|
|
|
+ <CardHeader>
|
|
|
+ <CardTitle className="flex items-center gap-2">
|
|
|
+ <Users className="h-5 w-5" />
|
|
|
+ Mis Secciones
|
|
|
+ </CardTitle>
|
|
|
+ <CardDescription>
|
|
|
+ Clases en las que estás inscrito este período
|
|
|
+ </CardDescription>
|
|
|
+ </CardHeader>
|
|
|
+ <CardContent>
|
|
|
+ <div className="space-y-4">
|
|
|
+ {sections.map((section) => (
|
|
|
+ <div key={section.id} className="flex items-center justify-between p-4 border rounded-lg">
|
|
|
+ <div className="flex items-center gap-4">
|
|
|
+ <div className="p-2 bg-primary/10 rounded-lg">
|
|
|
+ <BookOpen className="h-4 w-4 text-primary" />
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <h3 className="font-semibold">{section.className}</h3>
|
|
|
+ <p className="text-sm text-muted-foreground">
|
|
|
+ {section.classCode} - {section.name}
|
|
|
+ </p>
|
|
|
+ <p className="text-sm text-muted-foreground">
|
|
|
+ {section.teacherName} • {section.credits} créditos
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
+ <Badge variant={section.isActive ? 'default' : 'secondary'}>
|
|
|
+ {section.isActive ? 'Activa' : 'Inactiva'}
|
|
|
+ </Badge>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+ </div>
|
|
|
+ </DashboardLayout>
|
|
|
+ )
|
|
|
+}
|