|
@@ -0,0 +1,378 @@
|
|
|
|
|
+'use client';
|
|
|
|
|
+
|
|
|
|
|
+import { useState, useEffect } from 'react';
|
|
|
|
|
+import { Button } from '@/components/ui/button';
|
|
|
|
|
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
|
|
|
+import { Input } from '@/components/ui/input';
|
|
|
|
|
+import { Label } from '@/components/ui/label';
|
|
|
|
|
+import { Alert, AlertDescription } from '@/components/ui/alert';
|
|
|
|
|
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
|
|
|
+import { Trash2, Edit, Plus, UserCheck } from 'lucide-react';
|
|
|
|
|
+import { DashboardLayout } from '@/components/dashboard-layout';
|
|
|
|
|
+
|
|
|
|
|
+interface TeacherAssignment {
|
|
|
|
|
+ id: string;
|
|
|
|
|
+ teacherId: string;
|
|
|
|
|
+ teacherName: string;
|
|
|
|
|
+ teacherEmail: string;
|
|
|
|
|
+ classId: string;
|
|
|
|
|
+ className: string;
|
|
|
|
|
+ classCode: string;
|
|
|
|
|
+ sectionId: string;
|
|
|
|
|
+ sectionName: string;
|
|
|
|
|
+ periodName: string;
|
|
|
|
|
+ isActive: boolean;
|
|
|
|
|
+ createdAt: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface Teacher {
|
|
|
|
|
+ id: string;
|
|
|
|
|
+ firstName: string;
|
|
|
|
|
+ lastName: string;
|
|
|
|
|
+ email: string;
|
|
|
|
|
+ isActive?: boolean;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface Class {
|
|
|
|
|
+ id: string;
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ code: string;
|
|
|
|
|
+ periodId?: string;
|
|
|
|
|
+ isActive?: boolean;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface Section {
|
|
|
|
|
+ id: string;
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ classId: string;
|
|
|
|
|
+ className: string;
|
|
|
|
|
+ isActive?: boolean;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface FormData {
|
|
|
|
|
+ teacherId: string;
|
|
|
|
|
+ classId: string;
|
|
|
|
|
+ sectionId: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export default function TeacherAssignmentsPage() {
|
|
|
|
|
+ const [assignments, setAssignments] = useState<TeacherAssignment[]>([]);
|
|
|
|
|
+ const [teachers, setTeachers] = useState<Teacher[]>([]);
|
|
|
|
|
+ const [classes, setClasses] = useState<Class[]>([]);
|
|
|
|
|
+ const [sections, setSections] = useState<Section[]>([]);
|
|
|
|
|
+ const [filteredSections, setFilteredSections] = useState<Section[]>([]);
|
|
|
|
|
+ const [formData, setFormData] = useState<FormData>({
|
|
|
|
|
+ teacherId: '',
|
|
|
|
|
+ classId: '',
|
|
|
|
|
+ sectionId: ''
|
|
|
|
|
+ });
|
|
|
|
|
+ const [editingId, setEditingId] = useState<string | null>(null);
|
|
|
|
|
+ const [error, setError] = useState('');
|
|
|
|
|
+ const [success, setSuccess] = useState('');
|
|
|
|
|
+ const [loading, setLoading] = useState(false);
|
|
|
|
|
+
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ fetchAssignments();
|
|
|
|
|
+ fetchTeachers();
|
|
|
|
|
+ fetchClasses();
|
|
|
|
|
+ fetchSections();
|
|
|
|
|
+ }, []);
|
|
|
|
|
+
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ if (formData.classId) {
|
|
|
|
|
+ const classSections = sections.filter(s => s.classId === formData.classId && s.isActive !== false);
|
|
|
|
|
+ setFilteredSections(classSections);
|
|
|
|
|
+ setFormData(prev => ({ ...prev, sectionId: '' }));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ setFilteredSections([]);
|
|
|
|
|
+ }
|
|
|
|
|
+ }, [formData.classId, sections]);
|
|
|
|
|
+
|
|
|
|
|
+ const fetchAssignments = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch('/api/admin/teacher-assignments');
|
|
|
|
|
+ if (response.ok) {
|
|
|
|
|
+ const data = await response.json();
|
|
|
|
|
+ setAssignments(data);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Error fetching assignments:', error);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const fetchTeachers = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch('/api/admin/teachers');
|
|
|
|
|
+ if (response.ok) {
|
|
|
|
|
+ const data = await response.json();
|
|
|
|
|
+ setTeachers(data.filter((t: Teacher) => t.isActive !== false));
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Error fetching teachers:', error);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const fetchClasses = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch('/api/admin/classes');
|
|
|
|
|
+ if (response.ok) {
|
|
|
|
|
+ const data = await response.json();
|
|
|
|
|
+ setClasses(data.filter((c: Class) => c.isActive !== false));
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Error fetching classes:', error);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const fetchSections = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch('/api/admin/sections');
|
|
|
|
|
+ if (response.ok) {
|
|
|
|
|
+ const data = await response.json();
|
|
|
|
|
+ setSections(data.filter((s: Section) => s.isActive !== false));
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Error fetching sections:', error);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleSubmit = async (e: React.FormEvent) => {
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ setLoading(true);
|
|
|
|
|
+ setError('');
|
|
|
|
|
+ setSuccess('');
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const body = {
|
|
|
|
|
+ teacherId: formData.teacherId,
|
|
|
|
|
+ classId: formData.classId,
|
|
|
|
|
+ sectionId: formData.sectionId
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const url = editingId
|
|
|
|
|
+ ? `/api/admin/teacher-assignments/${editingId}`
|
|
|
|
|
+ : '/api/admin/teacher-assignments';
|
|
|
|
|
+
|
|
|
|
|
+ const method = editingId ? 'PUT' : 'POST';
|
|
|
|
|
+
|
|
|
|
|
+ const response = await fetch(url, {
|
|
|
|
|
+ method,
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
|
|
+ },
|
|
|
|
|
+ body: JSON.stringify(body),
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const result = await response.json();
|
|
|
|
|
+
|
|
|
|
|
+ if (response.ok) {
|
|
|
|
|
+ setSuccess(editingId ? 'Asignación actualizada exitosamente' : 'Asignación creada exitosamente');
|
|
|
|
|
+ setFormData({ teacherId: '', classId: '', sectionId: '' });
|
|
|
|
|
+ setEditingId(null);
|
|
|
|
|
+ fetchAssignments();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ setError(result.error || 'Error al procesar la asignación');
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ setError('Error de conexión');
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setLoading(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleEdit = (assignment: TeacherAssignment) => {
|
|
|
|
|
+ setFormData({
|
|
|
|
|
+ teacherId: assignment.teacherId,
|
|
|
|
|
+ classId: assignment.classId,
|
|
|
|
|
+ sectionId: assignment.sectionId
|
|
|
|
|
+ });
|
|
|
|
|
+ setEditingId(assignment.id);
|
|
|
|
|
+ setError('');
|
|
|
|
|
+ setSuccess('');
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleCancel = () => {
|
|
|
|
|
+ setFormData({ teacherId: '', classId: '', sectionId: '' });
|
|
|
|
|
+ setEditingId(null);
|
|
|
|
|
+ setError('');
|
|
|
|
|
+ setSuccess('');
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleDelete = async (id: string) => {
|
|
|
|
|
+ if (!confirm('¿Estás seguro de que deseas eliminar esta asignación?')) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch(`/api/admin/teacher-assignments/${id}`, {
|
|
|
|
|
+ method: 'DELETE',
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const result = await response.json();
|
|
|
|
|
+
|
|
|
|
|
+ if (response.ok) {
|
|
|
|
|
+ setSuccess('Asignación eliminada exitosamente');
|
|
|
|
|
+ fetchAssignments();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ setError(result.error || 'Error al eliminar la asignación');
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ setError('Error de conexión');
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <DashboardLayout>
|
|
|
|
|
+ <div className="space-y-6">
|
|
|
|
|
+ <div className="flex items-center justify-between">
|
|
|
|
|
+ <h1 className="text-3xl font-bold tracking-tight">Asignaciones de Profesores</h1>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ {error && (
|
|
|
|
|
+ <Alert variant="destructive">
|
|
|
|
|
+ <AlertDescription>{error}</AlertDescription>
|
|
|
|
|
+ </Alert>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ {success && (
|
|
|
|
|
+ <Alert>
|
|
|
|
|
+ <AlertDescription>{success}</AlertDescription>
|
|
|
|
|
+ </Alert>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ <div className="grid gap-6 md:grid-cols-2">
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <CardTitle className="flex items-center gap-2">
|
|
|
|
|
+ <Plus className="h-5 w-5" />
|
|
|
|
|
+ {editingId ? 'Editar Asignación' : 'Nueva Asignación'}
|
|
|
|
|
+ </CardTitle>
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent>
|
|
|
|
|
+ <form onSubmit={handleSubmit} className="space-y-4">
|
|
|
|
|
+ <div className="space-y-2">
|
|
|
|
|
+ <Label htmlFor="teacherId">Profesor</Label>
|
|
|
|
|
+ <Select
|
|
|
|
|
+ value={formData.teacherId}
|
|
|
|
|
+ onValueChange={(value) => setFormData({ ...formData, teacherId: value })}
|
|
|
|
|
+ >
|
|
|
|
|
+ <SelectTrigger>
|
|
|
|
|
+ <SelectValue placeholder="Seleccionar profesor" />
|
|
|
|
|
+ </SelectTrigger>
|
|
|
|
|
+ <SelectContent>
|
|
|
|
|
+ {teachers.map((teacher) => (
|
|
|
|
|
+ <SelectItem key={teacher.id} value={teacher.id}>
|
|
|
|
|
+ {teacher.firstName} {teacher.lastName} ({teacher.email})
|
|
|
|
|
+ </SelectItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SelectContent>
|
|
|
|
|
+ </Select>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div className="space-y-2">
|
|
|
|
|
+ <Label htmlFor="classId">Clase</Label>
|
|
|
|
|
+ <Select
|
|
|
|
|
+ value={formData.classId}
|
|
|
|
|
+ onValueChange={(value) => setFormData({ ...formData, classId: value })}
|
|
|
|
|
+ >
|
|
|
|
|
+ <SelectTrigger>
|
|
|
|
|
+ <SelectValue placeholder="Seleccionar clase" />
|
|
|
|
|
+ </SelectTrigger>
|
|
|
|
|
+ <SelectContent>
|
|
|
|
|
+ {classes.map((cls) => (
|
|
|
|
|
+ <SelectItem key={cls.id} value={cls.id}>
|
|
|
|
|
+ {cls.code} - {cls.name}
|
|
|
|
|
+ </SelectItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SelectContent>
|
|
|
|
|
+ </Select>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div className="space-y-2">
|
|
|
|
|
+ <Label htmlFor="sectionId">Sección</Label>
|
|
|
|
|
+ <Select
|
|
|
|
|
+ value={formData.sectionId}
|
|
|
|
|
+ onValueChange={(value) => setFormData({ ...formData, sectionId: value })}
|
|
|
|
|
+ disabled={!formData.classId}
|
|
|
|
|
+ >
|
|
|
|
|
+ <SelectTrigger>
|
|
|
|
|
+ <SelectValue placeholder={formData.classId ? "Seleccionar sección" : "Primero selecciona una clase"} />
|
|
|
|
|
+ </SelectTrigger>
|
|
|
|
|
+ <SelectContent>
|
|
|
|
|
+ {filteredSections.map((section) => (
|
|
|
|
|
+ <SelectItem key={section.id} value={section.id}>
|
|
|
|
|
+ {section.name}
|
|
|
|
|
+ </SelectItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SelectContent>
|
|
|
|
|
+ </Select>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div className="flex gap-2">
|
|
|
|
|
+ <Button type="submit" disabled={loading}>
|
|
|
|
|
+ {loading ? 'Procesando...' : (editingId ? 'Actualizar' : 'Crear')}
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ {editingId && (
|
|
|
|
|
+ <Button type="button" variant="outline" onClick={handleCancel}>
|
|
|
|
|
+ Cancelar
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </form>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <CardTitle className="flex items-center gap-2">
|
|
|
|
|
+ <UserCheck className="h-5 w-5" />
|
|
|
|
|
+ Asignaciones Registradas
|
|
|
|
|
+ </CardTitle>
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent>
|
|
|
|
|
+ <div className="space-y-4">
|
|
|
|
|
+ {assignments.length === 0 ? (
|
|
|
|
|
+ <p className="text-muted-foreground text-center py-4">
|
|
|
|
|
+ No hay asignaciones registradas
|
|
|
|
|
+ </p>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ assignments.map((assignment) => (
|
|
|
|
|
+ <div key={assignment.id} className="flex items-center justify-between p-3 border rounded-lg">
|
|
|
|
|
+ <div className="flex-1">
|
|
|
|
|
+ <div className="font-medium">
|
|
|
|
|
+ {assignment.teacherName}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="text-sm text-muted-foreground">
|
|
|
|
|
+ {assignment.classCode} - {assignment.className}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="text-sm text-muted-foreground">
|
|
|
|
|
+ Sección: {assignment.sectionName} | Período: {assignment.periodName}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="flex gap-2">
|
|
|
|
|
+ <Button
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ onClick={() => handleEdit(assignment)}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Edit className="h-4 w-4" />
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ onClick={() => handleDelete(assignment.id)}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Trash2 className="h-4 w-4" />
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ))
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </DashboardLayout>
|
|
|
|
|
+ );
|
|
|
|
|
+}
|