|
|
@@ -0,0 +1,559 @@
|
|
|
+'use client';
|
|
|
+
|
|
|
+import { useState, useEffect } from 'react';
|
|
|
+import { MainLayout } from '@/components/layout/main-layout';
|
|
|
+import { Button } from '@/components/ui/button';
|
|
|
+import { Input } from '@/components/ui/input';
|
|
|
+import { Label } from '@/components/ui/label';
|
|
|
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
|
+import { Badge } from '@/components/ui/badge';
|
|
|
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
|
+import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
|
|
|
+import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog';
|
|
|
+import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
|
|
+import { Switch } from '@/components/ui/switch';
|
|
|
+import { toast } from 'sonner';
|
|
|
+import { Plus, Search, Edit, Trash2, Users, GraduationCap, BookOpen, UserCheck } from 'lucide-react';
|
|
|
+
|
|
|
+interface Student {
|
|
|
+ id: string;
|
|
|
+ firstName: string;
|
|
|
+ lastName: string;
|
|
|
+ cedula: string;
|
|
|
+ email: string;
|
|
|
+ phone: string;
|
|
|
+ admissionNumber: string;
|
|
|
+}
|
|
|
+
|
|
|
+interface Section {
|
|
|
+ id: string;
|
|
|
+ name: string;
|
|
|
+ class: {
|
|
|
+ id: string;
|
|
|
+ name: string;
|
|
|
+ code: string;
|
|
|
+ period: {
|
|
|
+ id: string;
|
|
|
+ name: string;
|
|
|
+ isActive: boolean;
|
|
|
+ };
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+interface StudentEnrollment {
|
|
|
+ id: string;
|
|
|
+ studentId: string;
|
|
|
+ sectionId: string;
|
|
|
+ isActive: boolean;
|
|
|
+ createdAt: string;
|
|
|
+ updatedAt: string;
|
|
|
+ student: Student;
|
|
|
+ section: Section;
|
|
|
+}
|
|
|
+
|
|
|
+interface CreateEnrollmentData {
|
|
|
+ studentId: string;
|
|
|
+ sectionId: string;
|
|
|
+}
|
|
|
+
|
|
|
+export default function StudentEnrollmentsPage() {
|
|
|
+ const [enrollments, setEnrollments] = useState<StudentEnrollment[]>([]);
|
|
|
+ const [students, setStudents] = useState<Student[]>([]);
|
|
|
+ const [sections, setSections] = useState<Section[]>([]);
|
|
|
+ const [loading, setLoading] = useState(true);
|
|
|
+ const [searchTerm, setSearchTerm] = useState('');
|
|
|
+ const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
|
|
|
+ const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
|
|
|
+ const [editingEnrollment, setEditingEnrollment] = useState<StudentEnrollment | null>(null);
|
|
|
+ const [formData, setFormData] = useState<CreateEnrollmentData>({
|
|
|
+ studentId: '',
|
|
|
+ sectionId: '',
|
|
|
+ });
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ fetchEnrollments();
|
|
|
+ fetchStudents();
|
|
|
+ fetchSections();
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ const fetchEnrollments = async () => {
|
|
|
+ try {
|
|
|
+ const response = await fetch('/api/admin/student-enrollments');
|
|
|
+ if (response.ok) {
|
|
|
+ const data = await response.json();
|
|
|
+ setEnrollments(data);
|
|
|
+ } else {
|
|
|
+ toast.error('Error al cargar las inscripciones');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error fetching enrollments:', error);
|
|
|
+ toast.error('Error al cargar las inscripciones');
|
|
|
+ } finally {
|
|
|
+ setLoading(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const fetchStudents = async () => {
|
|
|
+ try {
|
|
|
+ const response = await fetch('/api/admin/users');
|
|
|
+ if (response.ok) {
|
|
|
+ const users = await response.json();
|
|
|
+ const studentUsers = users.filter((user: any) => user.role === 'STUDENT' && user.student);
|
|
|
+ const studentsData = studentUsers.map((user: any) => ({
|
|
|
+ id: user.student.id,
|
|
|
+ firstName: user.student.firstName,
|
|
|
+ lastName: user.student.lastName,
|
|
|
+ cedula: user.student.cedula,
|
|
|
+ email: user.student.email,
|
|
|
+ phone: user.student.phone,
|
|
|
+ admissionNumber: user.student.admissionNumber,
|
|
|
+ }));
|
|
|
+ setStudents(studentsData);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error fetching students:', error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const fetchSections = async () => {
|
|
|
+ try {
|
|
|
+ const response = await fetch('/api/admin/sections');
|
|
|
+ if (response.ok) {
|
|
|
+ const data = await response.json();
|
|
|
+ setSections(data);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error fetching sections:', error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleCreateEnrollment = async () => {
|
|
|
+ if (!validateForm()) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch('/api/admin/student-enrollments', {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ },
|
|
|
+ body: JSON.stringify(formData),
|
|
|
+ });
|
|
|
+
|
|
|
+ const result = await response.json();
|
|
|
+
|
|
|
+ if (response.ok) {
|
|
|
+ toast.success('Inscripción creada exitosamente');
|
|
|
+ setIsCreateDialogOpen(false);
|
|
|
+ resetForm();
|
|
|
+ fetchEnrollments();
|
|
|
+ } else {
|
|
|
+ toast.error(result.message || 'Error al crear la inscripción');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error creating enrollment:', error);
|
|
|
+ toast.error('Error al crear la inscripción');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleUpdateEnrollment = async () => {
|
|
|
+ if (!editingEnrollment || !validateForm()) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch(`/api/admin/student-enrollments/${editingEnrollment.id}`, {
|
|
|
+ method: 'PUT',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ },
|
|
|
+ body: JSON.stringify({
|
|
|
+ ...formData,
|
|
|
+ isActive: editingEnrollment.isActive,
|
|
|
+ }),
|
|
|
+ });
|
|
|
+
|
|
|
+ const result = await response.json();
|
|
|
+
|
|
|
+ if (response.ok) {
|
|
|
+ toast.success('Inscripción actualizada exitosamente');
|
|
|
+ setIsEditDialogOpen(false);
|
|
|
+ setEditingEnrollment(null);
|
|
|
+ resetForm();
|
|
|
+ fetchEnrollments();
|
|
|
+ } else {
|
|
|
+ toast.error(result.message || 'Error al actualizar la inscripción');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error updating enrollment:', error);
|
|
|
+ toast.error('Error al actualizar la inscripción');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleDeleteEnrollment = async (enrollmentId: string) => {
|
|
|
+ try {
|
|
|
+ const response = await fetch(`/api/admin/student-enrollments/${enrollmentId}`, {
|
|
|
+ method: 'DELETE',
|
|
|
+ });
|
|
|
+
|
|
|
+ const result = await response.json();
|
|
|
+
|
|
|
+ if (response.ok) {
|
|
|
+ toast.success(result.message || 'Inscripción eliminada exitosamente');
|
|
|
+ fetchEnrollments();
|
|
|
+ } else {
|
|
|
+ toast.error(result.message || 'Error al eliminar la inscripción');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error deleting enrollment:', error);
|
|
|
+ toast.error('Error al eliminar la inscripción');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleToggleActive = async (enrollment: StudentEnrollment) => {
|
|
|
+ try {
|
|
|
+ const response = await fetch(`/api/admin/student-enrollments/${enrollment.id}`, {
|
|
|
+ method: 'PUT',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ },
|
|
|
+ body: JSON.stringify({
|
|
|
+ studentId: enrollment.studentId,
|
|
|
+ sectionId: enrollment.sectionId,
|
|
|
+ isActive: !enrollment.isActive,
|
|
|
+ }),
|
|
|
+ });
|
|
|
+
|
|
|
+ const result = await response.json();
|
|
|
+
|
|
|
+ if (response.ok) {
|
|
|
+ toast.success(`Inscripción ${!enrollment.isActive ? 'activada' : 'desactivada'} exitosamente`);
|
|
|
+ fetchEnrollments();
|
|
|
+ } else {
|
|
|
+ toast.error(result.message || 'Error al cambiar el estado de la inscripción');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error toggling enrollment status:', error);
|
|
|
+ toast.error('Error al cambiar el estado de la inscripción');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const validateForm = (): boolean => {
|
|
|
+ if (!formData.studentId) {
|
|
|
+ toast.error('Por favor selecciona un estudiante');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (!formData.sectionId) {
|
|
|
+ toast.error('Por favor selecciona una sección');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ };
|
|
|
+
|
|
|
+ const resetForm = () => {
|
|
|
+ setFormData({
|
|
|
+ studentId: '',
|
|
|
+ sectionId: '',
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ const openEditDialog = (enrollment: StudentEnrollment) => {
|
|
|
+ setEditingEnrollment(enrollment);
|
|
|
+ setFormData({
|
|
|
+ studentId: enrollment.studentId,
|
|
|
+ sectionId: enrollment.sectionId,
|
|
|
+ });
|
|
|
+ setIsEditDialogOpen(true);
|
|
|
+ };
|
|
|
+
|
|
|
+ const filteredEnrollments = enrollments.filter(enrollment => {
|
|
|
+ const searchLower = searchTerm.toLowerCase();
|
|
|
+ const studentName = `${enrollment.student.firstName} ${enrollment.student.lastName}`.toLowerCase();
|
|
|
+ const studentCedula = enrollment.student.cedula.toLowerCase();
|
|
|
+ const admissionNumber = enrollment.student.admissionNumber.toLowerCase();
|
|
|
+ const sectionName = enrollment.section.name.toLowerCase();
|
|
|
+ const className = enrollment.section.class.name.toLowerCase();
|
|
|
+ const classCode = enrollment.section.class.code.toLowerCase();
|
|
|
+
|
|
|
+ return studentName.includes(searchLower) ||
|
|
|
+ studentCedula.includes(searchLower) ||
|
|
|
+ admissionNumber.includes(searchLower) ||
|
|
|
+ sectionName.includes(searchLower) ||
|
|
|
+ className.includes(searchLower) ||
|
|
|
+ classCode.includes(searchLower);
|
|
|
+ });
|
|
|
+
|
|
|
+ const getStatusBadgeVariant = (isActive: boolean) => {
|
|
|
+ return isActive ? 'default' : 'secondary';
|
|
|
+ };
|
|
|
+
|
|
|
+ const getStudentName = (student: Student) => {
|
|
|
+ return `${student.firstName} ${student.lastName}`;
|
|
|
+ };
|
|
|
+
|
|
|
+ const getSectionDisplay = (section: Section) => {
|
|
|
+ return `${section.class.code} - ${section.class.name} (${section.name})`;
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <MainLayout>
|
|
|
+ <div className="space-y-6">
|
|
|
+ <div className="flex items-center justify-between">
|
|
|
+ <div>
|
|
|
+ <h1 className="text-3xl font-bold tracking-tight">Inscripciones de Estudiantes</h1>
|
|
|
+ <p className="text-muted-foreground">
|
|
|
+ Gestiona las inscripciones de estudiantes a secciones
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ <Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
|
|
|
+ <DialogTrigger asChild>
|
|
|
+ <Button onClick={() => resetForm()}>
|
|
|
+ <Plus className="mr-2 h-4 w-4" />
|
|
|
+ Nueva Inscripción
|
|
|
+ </Button>
|
|
|
+ </DialogTrigger>
|
|
|
+ <DialogContent className="sm:max-w-[425px]">
|
|
|
+ <DialogHeader>
|
|
|
+ <DialogTitle>Crear Nueva Inscripción</DialogTitle>
|
|
|
+ <DialogDescription>
|
|
|
+ Inscribe un estudiante a una sección específica.
|
|
|
+ </DialogDescription>
|
|
|
+ </DialogHeader>
|
|
|
+ <div className="grid gap-4 py-4">
|
|
|
+ <div className="grid gap-2">
|
|
|
+ <Label htmlFor="student">Estudiante</Label>
|
|
|
+ <Select
|
|
|
+ value={formData.studentId}
|
|
|
+ onValueChange={(value) => setFormData({ ...formData, studentId: value })}
|
|
|
+ >
|
|
|
+ <SelectTrigger>
|
|
|
+ <SelectValue placeholder="Selecciona un estudiante" />
|
|
|
+ </SelectTrigger>
|
|
|
+ <SelectContent>
|
|
|
+ {students.map((student) => (
|
|
|
+ <SelectItem key={student.id} value={student.id}>
|
|
|
+ {getStudentName(student)} - {student.admissionNumber}
|
|
|
+ </SelectItem>
|
|
|
+ ))}
|
|
|
+ </SelectContent>
|
|
|
+ </Select>
|
|
|
+ </div>
|
|
|
+ <div className="grid gap-2">
|
|
|
+ <Label htmlFor="section">Sección</Label>
|
|
|
+ <Select
|
|
|
+ value={formData.sectionId}
|
|
|
+ onValueChange={(value) => setFormData({ ...formData, sectionId: value })}
|
|
|
+ >
|
|
|
+ <SelectTrigger>
|
|
|
+ <SelectValue placeholder="Selecciona una sección" />
|
|
|
+ </SelectTrigger>
|
|
|
+ <SelectContent>
|
|
|
+ {sections
|
|
|
+ .filter(section => section.class.period.isActive)
|
|
|
+ .map((section) => (
|
|
|
+ <SelectItem key={section.id} value={section.id}>
|
|
|
+ {getSectionDisplay(section)}
|
|
|
+ </SelectItem>
|
|
|
+ ))}
|
|
|
+ </SelectContent>
|
|
|
+ </Select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <DialogFooter>
|
|
|
+ <Button type="submit" onClick={handleCreateEnrollment}>
|
|
|
+ Crear Inscripción
|
|
|
+ </Button>
|
|
|
+ </DialogFooter>
|
|
|
+ </DialogContent>
|
|
|
+ </Dialog>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <Card>
|
|
|
+ <CardHeader>
|
|
|
+ <CardTitle className="flex items-center gap-2">
|
|
|
+ <UserCheck className="h-5 w-5" />
|
|
|
+ Inscripciones del Sistema
|
|
|
+ </CardTitle>
|
|
|
+ <CardDescription>
|
|
|
+ Lista de todas las inscripciones de estudiantes a secciones
|
|
|
+ </CardDescription>
|
|
|
+ <div className="flex items-center space-x-2">
|
|
|
+ <Search className="h-4 w-4 text-muted-foreground" />
|
|
|
+ <Input
|
|
|
+ placeholder="Buscar por estudiante, cédula, matrícula o sección..."
|
|
|
+ value={searchTerm}
|
|
|
+ onChange={(e) => setSearchTerm(e.target.value)}
|
|
|
+ className="max-w-sm"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </CardHeader>
|
|
|
+ <CardContent>
|
|
|
+ {loading ? (
|
|
|
+ <div className="flex items-center justify-center py-8">
|
|
|
+ <div className="text-muted-foreground">Cargando inscripciones...</div>
|
|
|
+ </div>
|
|
|
+ ) : (
|
|
|
+ <Table>
|
|
|
+ <TableHeader>
|
|
|
+ <TableRow>
|
|
|
+ <TableHead>Estudiante</TableHead>
|
|
|
+ <TableHead>Cédula</TableHead>
|
|
|
+ <TableHead>Matrícula</TableHead>
|
|
|
+ <TableHead>Materia</TableHead>
|
|
|
+ <TableHead>Sección</TableHead>
|
|
|
+ <TableHead>Periodo</TableHead>
|
|
|
+ <TableHead>Estado</TableHead>
|
|
|
+ <TableHead>Fecha Inscripción</TableHead>
|
|
|
+ <TableHead className="text-right">Acciones</TableHead>
|
|
|
+ </TableRow>
|
|
|
+ </TableHeader>
|
|
|
+ <TableBody>
|
|
|
+ {filteredEnrollments.length === 0 ? (
|
|
|
+ <TableRow>
|
|
|
+ <TableCell colSpan={9} className="text-center py-8 text-muted-foreground">
|
|
|
+ {searchTerm ? 'No se encontraron inscripciones que coincidan con la búsqueda' : 'No hay inscripciones registradas'}
|
|
|
+ </TableCell>
|
|
|
+ </TableRow>
|
|
|
+ ) : (
|
|
|
+ filteredEnrollments.map((enrollment) => (
|
|
|
+ <TableRow key={enrollment.id}>
|
|
|
+ <TableCell className="font-medium">
|
|
|
+ {getStudentName(enrollment.student)}
|
|
|
+ </TableCell>
|
|
|
+ <TableCell>{enrollment.student.cedula}</TableCell>
|
|
|
+ <TableCell>
|
|
|
+ <Badge variant="outline">
|
|
|
+ {enrollment.student.admissionNumber}
|
|
|
+ </Badge>
|
|
|
+ </TableCell>
|
|
|
+ <TableCell>
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
+ <BookOpen className="h-4 w-4 text-muted-foreground" />
|
|
|
+ <div>
|
|
|
+ <div className="font-medium">{enrollment.section.class.name}</div>
|
|
|
+ <div className="text-sm text-muted-foreground">{enrollment.section.class.code}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </TableCell>
|
|
|
+ <TableCell>{enrollment.section.name}</TableCell>
|
|
|
+ <TableCell>
|
|
|
+ <Badge variant={enrollment.section.class.period.isActive ? 'default' : 'secondary'}>
|
|
|
+ {enrollment.section.class.period.name}
|
|
|
+ </Badge>
|
|
|
+ </TableCell>
|
|
|
+ <TableCell>
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
+ <Switch
|
|
|
+ checked={enrollment.isActive}
|
|
|
+ onCheckedChange={() => handleToggleActive(enrollment)}
|
|
|
+ />
|
|
|
+ <Badge variant={getStatusBadgeVariant(enrollment.isActive)}>
|
|
|
+ {enrollment.isActive ? 'Activa' : 'Inactiva'}
|
|
|
+ </Badge>
|
|
|
+ </div>
|
|
|
+ </TableCell>
|
|
|
+ <TableCell>
|
|
|
+ {new Date(enrollment.createdAt).toLocaleDateString('es-ES')}
|
|
|
+ </TableCell>
|
|
|
+ <TableCell className="text-right">
|
|
|
+ <div className="flex items-center justify-end gap-2">
|
|
|
+ <Button
|
|
|
+ variant="outline"
|
|
|
+ size="sm"
|
|
|
+ onClick={() => openEditDialog(enrollment)}
|
|
|
+ >
|
|
|
+ <Edit className="h-4 w-4" />
|
|
|
+ </Button>
|
|
|
+ <AlertDialog>
|
|
|
+ <AlertDialogTrigger asChild>
|
|
|
+ <Button variant="outline" size="sm">
|
|
|
+ <Trash2 className="h-4 w-4" />
|
|
|
+ </Button>
|
|
|
+ </AlertDialogTrigger>
|
|
|
+ <AlertDialogContent>
|
|
|
+ <AlertDialogHeader>
|
|
|
+ <AlertDialogTitle>¿Estás seguro?</AlertDialogTitle>
|
|
|
+ <AlertDialogDescription>
|
|
|
+ Esta acción eliminará la inscripción de {getStudentName(enrollment.student)} en la sección {enrollment.section.name}.
|
|
|
+ {enrollment.isActive && ' Si existen registros de asistencia, la inscripción será desactivada en lugar de eliminada.'}
|
|
|
+ </AlertDialogDescription>
|
|
|
+ </AlertDialogHeader>
|
|
|
+ <AlertDialogFooter>
|
|
|
+ <AlertDialogCancel>Cancelar</AlertDialogCancel>
|
|
|
+ <AlertDialogAction
|
|
|
+ onClick={() => handleDeleteEnrollment(enrollment.id)}
|
|
|
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
|
+ >
|
|
|
+ Eliminar
|
|
|
+ </AlertDialogAction>
|
|
|
+ </AlertDialogFooter>
|
|
|
+ </AlertDialogContent>
|
|
|
+ </AlertDialog>
|
|
|
+ </div>
|
|
|
+ </TableCell>
|
|
|
+ </TableRow>
|
|
|
+ ))
|
|
|
+ )}
|
|
|
+ </TableBody>
|
|
|
+ </Table>
|
|
|
+ )}
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+
|
|
|
+ {/* Edit Dialog */}
|
|
|
+ <Dialog open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen}>
|
|
|
+ <DialogContent className="sm:max-w-[425px]">
|
|
|
+ <DialogHeader>
|
|
|
+ <DialogTitle>Editar Inscripción</DialogTitle>
|
|
|
+ <DialogDescription>
|
|
|
+ Modifica los detalles de la inscripción.
|
|
|
+ </DialogDescription>
|
|
|
+ </DialogHeader>
|
|
|
+ <div className="grid gap-4 py-4">
|
|
|
+ <div className="grid gap-2">
|
|
|
+ <Label htmlFor="edit-student">Estudiante</Label>
|
|
|
+ <Select
|
|
|
+ value={formData.studentId}
|
|
|
+ onValueChange={(value) => setFormData({ ...formData, studentId: value })}
|
|
|
+ >
|
|
|
+ <SelectTrigger>
|
|
|
+ <SelectValue placeholder="Selecciona un estudiante" />
|
|
|
+ </SelectTrigger>
|
|
|
+ <SelectContent>
|
|
|
+ {students.map((student) => (
|
|
|
+ <SelectItem key={student.id} value={student.id}>
|
|
|
+ {getStudentName(student)} - {student.admissionNumber}
|
|
|
+ </SelectItem>
|
|
|
+ ))}
|
|
|
+ </SelectContent>
|
|
|
+ </Select>
|
|
|
+ </div>
|
|
|
+ <div className="grid gap-2">
|
|
|
+ <Label htmlFor="edit-section">Sección</Label>
|
|
|
+ <Select
|
|
|
+ value={formData.sectionId}
|
|
|
+ onValueChange={(value) => setFormData({ ...formData, sectionId: value })}
|
|
|
+ >
|
|
|
+ <SelectTrigger>
|
|
|
+ <SelectValue placeholder="Selecciona una sección" />
|
|
|
+ </SelectTrigger>
|
|
|
+ <SelectContent>
|
|
|
+ {sections
|
|
|
+ .filter(section => section.class.period.isActive)
|
|
|
+ .map((section) => (
|
|
|
+ <SelectItem key={section.id} value={section.id}>
|
|
|
+ {getSectionDisplay(section)}
|
|
|
+ </SelectItem>
|
|
|
+ ))}
|
|
|
+ </SelectContent>
|
|
|
+ </Select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <DialogFooter>
|
|
|
+ <Button type="submit" onClick={handleUpdateEnrollment}>
|
|
|
+ Actualizar Inscripción
|
|
|
+ </Button>
|
|
|
+ </DialogFooter>
|
|
|
+ </DialogContent>
|
|
|
+ </Dialog>
|
|
|
+ </div>
|
|
|
+ </MainLayout>
|
|
|
+ );
|
|
|
+}
|