|
@@ -0,0 +1,234 @@
|
|
|
|
|
+import { getServerSession } from 'next-auth'
|
|
|
|
|
+import { authOptions } from '@/lib/auth'
|
|
|
|
|
+import { redirect } from 'next/navigation'
|
|
|
|
|
+import { db } from '@/lib/db'
|
|
|
|
|
+import { teacherAssignments, sections, classes, periods, studentEnrollments, attendance, eq, and } from '@/lib/db/schema'
|
|
|
|
|
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
|
|
|
+import { BookOpen, Users, ClipboardCheck, Calendar } from 'lucide-react'
|
|
|
|
|
+import { DashboardLayout } from '@/components/dashboard-layout'
|
|
|
|
|
+
|
|
|
|
|
+interface TeacherStats {
|
|
|
|
|
+ totalSections: number
|
|
|
|
|
+ totalStudents: number
|
|
|
|
|
+ attendanceRecords: number
|
|
|
|
|
+ activePeriods: number
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface AssignedSection {
|
|
|
|
|
+ id: string
|
|
|
|
|
+ name: string
|
|
|
|
|
+ className: string
|
|
|
|
|
+ periodName: string
|
|
|
|
|
+ studentCount: number
|
|
|
|
|
+ maxStudents: number
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function getTeacherStats(teacherId: string): Promise<TeacherStats> {
|
|
|
|
|
+ // Get teacher's assigned sections
|
|
|
|
|
+ const assignedSections = await db
|
|
|
|
|
+ .select({ sectionId: teacherAssignments.sectionId })
|
|
|
|
|
+ .from(teacherAssignments)
|
|
|
|
|
+ .where(eq(teacherAssignments.teacherId, teacherId))
|
|
|
|
|
+
|
|
|
|
|
+ const sectionIds = assignedSections.map(a => a.sectionId)
|
|
|
|
|
+
|
|
|
|
|
+ if (sectionIds.length === 0) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ totalSections: 0,
|
|
|
|
|
+ totalStudents: 0,
|
|
|
|
|
+ attendanceRecords: 0,
|
|
|
|
|
+ activePeriods: 0
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Count total students enrolled in teacher's sections
|
|
|
|
|
+ const studentCount = await db
|
|
|
|
|
+ .select()
|
|
|
|
|
+ .from(studentEnrollments)
|
|
|
|
|
+ .where(eq(studentEnrollments.sectionId, sectionIds[0] as string)) // This is a simplified query
|
|
|
|
|
+
|
|
|
|
|
+ // Count attendance records for teacher's sections
|
|
|
|
|
+ const attendanceCount = await db
|
|
|
|
|
+ .select()
|
|
|
|
|
+ .from(attendance)
|
|
|
|
|
+ .where(eq(attendance.sectionId, sectionIds[0] as string)) // This is a simplified query
|
|
|
|
|
+
|
|
|
|
|
+ // Count active periods
|
|
|
|
|
+ const activePeriods = await db
|
|
|
|
|
+ .select()
|
|
|
|
|
+ .from(periods)
|
|
|
|
|
+ .where(eq(periods.isActive, true))
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ totalSections: sectionIds.length,
|
|
|
|
|
+ totalStudents: studentCount.length,
|
|
|
|
|
+ attendanceRecords: attendanceCount.length,
|
|
|
|
|
+ activePeriods: activePeriods.length
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function getAssignedSections(teacherId: string): Promise<AssignedSection[]> {
|
|
|
|
|
+ const result = await db
|
|
|
|
|
+ .select({
|
|
|
|
|
+ sectionId: sections.id,
|
|
|
|
|
+ sectionName: sections.name,
|
|
|
|
|
+ className: classes.name,
|
|
|
|
|
+ periodName: periods.name,
|
|
|
|
|
+ maxStudents: sections.maxStudents
|
|
|
|
|
+ })
|
|
|
|
|
+ .from(teacherAssignments)
|
|
|
|
|
+ .innerJoin(sections, eq(teacherAssignments.sectionId, sections.id))
|
|
|
|
|
+ .innerJoin(classes, eq(sections.classId, classes.id))
|
|
|
|
|
+ .innerJoin(periods, eq(sections.periodId, periods.id))
|
|
|
|
|
+ .where(eq(teacherAssignments.teacherId, teacherId))
|
|
|
|
|
+
|
|
|
|
|
+ // Get student count for each section
|
|
|
|
|
+ const sectionsWithStudents = await Promise.all(
|
|
|
|
|
+ result.map(async (section) => {
|
|
|
|
|
+ const studentCount = await db
|
|
|
|
|
+ .select()
|
|
|
|
|
+ .from(studentEnrollments)
|
|
|
|
|
+ .where(eq(studentEnrollments.sectionId, section.sectionId))
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ id: section.sectionId,
|
|
|
|
|
+ name: section.sectionName,
|
|
|
|
|
+ className: section.className,
|
|
|
|
|
+ periodName: section.periodName,
|
|
|
|
|
+ studentCount: studentCount.length,
|
|
|
|
|
+ maxStudents: section.maxStudents || 0
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ return sectionsWithStudents
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export default async function TeacherDashboard() {
|
|
|
|
|
+ const session = await getServerSession(authOptions)
|
|
|
|
|
+
|
|
|
|
|
+ if (!session || session.user.role !== 'teacher') {
|
|
|
|
|
+ redirect('/auth/signin')
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const stats = await getTeacherStats(session.user.id)
|
|
|
|
|
+ const assignedSections = await getAssignedSections(session.user.id)
|
|
|
|
|
+
|
|
|
|
|
+ const breadcrumbs = [
|
|
|
|
|
+ { label: "Dashboard" }
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <DashboardLayout breadcrumbs={breadcrumbs}>
|
|
|
|
|
+ <div className="space-y-6">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <h1 className="text-2xl font-bold text-gray-900">
|
|
|
|
|
+ Bienvenido, {session.user.firstName} {session.user.lastName}
|
|
|
|
|
+ </h1>
|
|
|
|
|
+ <p className="text-gray-600">
|
|
|
|
|
+ Gestiona tus clases y estudiantes desde aquí
|
|
|
|
|
+ </p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* Stats Cards */}
|
|
|
|
|
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
|
|
|
+ <CardTitle className="text-sm font-medium">
|
|
|
|
|
+ Secciones Asignadas
|
|
|
|
|
+ </CardTitle>
|
|
|
|
|
+ <BookOpen className="h-4 w-4 text-muted-foreground" />
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent>
|
|
|
|
|
+ <div className="text-2xl font-bold">{stats.totalSections}</div>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
|
|
|
+ <CardTitle className="text-sm font-medium">
|
|
|
|
|
+ Total Estudiantes
|
|
|
|
|
+ </CardTitle>
|
|
|
|
|
+ <Users className="h-4 w-4 text-muted-foreground" />
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent>
|
|
|
|
|
+ <div className="text-2xl font-bold">{stats.totalStudents}</div>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
|
|
|
+ <CardTitle className="text-sm font-medium">
|
|
|
|
|
+ Registros de Asistencia
|
|
|
|
|
+ </CardTitle>
|
|
|
|
|
+ <ClipboardCheck className="h-4 w-4 text-muted-foreground" />
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent>
|
|
|
|
|
+ <div className="text-2xl font-bold">{stats.attendanceRecords}</div>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
|
|
|
+ <CardTitle className="text-sm font-medium">
|
|
|
|
|
+ Períodos Activos
|
|
|
|
|
+ </CardTitle>
|
|
|
|
|
+ <Calendar className="h-4 w-4 text-muted-foreground" />
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent>
|
|
|
|
|
+ <div className="text-2xl font-bold">{stats.activePeriods}</div>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* Assigned Sections */}
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <CardTitle>Mis Secciones</CardTitle>
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent>
|
|
|
|
|
+ {assignedSections.length === 0 ? (
|
|
|
|
|
+ <p className="text-gray-500 text-center py-8">
|
|
|
|
|
+ No tienes secciones asignadas actualmente.
|
|
|
|
|
+ </p>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <div className="space-y-4">
|
|
|
|
|
+ {assignedSections.map((section) => (
|
|
|
|
|
+ <div
|
|
|
|
|
+ key={section.id}
|
|
|
|
|
+ className="flex items-center justify-between p-4 border rounded-lg hover:bg-gray-50"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <h3 className="font-medium">
|
|
|
|
|
+ {section.className} - {section.name}
|
|
|
|
|
+ </h3>
|
|
|
|
|
+ <p className="text-sm text-gray-600">
|
|
|
|
|
+ Período: {section.periodName}
|
|
|
|
|
+ </p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="text-right">
|
|
|
|
|
+ <p className="text-sm font-medium">
|
|
|
|
|
+ {section.studentCount}/{section.maxStudents} estudiantes
|
|
|
|
|
+ </p>
|
|
|
|
|
+ <div className="w-24 bg-gray-200 rounded-full h-2 mt-1">
|
|
|
|
|
+ <div
|
|
|
|
|
+ className="bg-blue-600 h-2 rounded-full"
|
|
|
|
|
+ style={{
|
|
|
|
|
+ width: `${Math.min(
|
|
|
|
|
+ (section.studentCount / section.maxStudents) * 100,
|
|
|
|
|
+ 100
|
|
|
|
|
+ )}%`
|
|
|
|
|
+ }}
|
|
|
|
|
+ ></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </DashboardLayout>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|