|
@@ -0,0 +1,547 @@
|
|
|
|
|
+'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 } from 'lucide-react';
|
|
|
|
|
+
|
|
|
|
|
+interface Teacher {
|
|
|
|
|
+ id: string;
|
|
|
|
|
+ firstName: string;
|
|
|
|
|
+ lastName: string;
|
|
|
|
|
+ cedula: string;
|
|
|
|
|
+ email: string;
|
|
|
|
|
+ phone: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface Section {
|
|
|
|
|
+ id: string;
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ class: {
|
|
|
|
|
+ id: string;
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ code: string;
|
|
|
|
|
+ period: {
|
|
|
|
|
+ id: string;
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ isActive: boolean;
|
|
|
|
|
+ };
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface TeacherAssignment {
|
|
|
|
|
+ id: string;
|
|
|
|
|
+ teacherId: string;
|
|
|
|
|
+ sectionId: string;
|
|
|
|
|
+ isActive: boolean;
|
|
|
|
|
+ createdAt: string;
|
|
|
|
|
+ updatedAt: string;
|
|
|
|
|
+ teacher: Teacher;
|
|
|
|
|
+ section: Section;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface CreateAssignmentData {
|
|
|
|
|
+ teacherId: string;
|
|
|
|
|
+ sectionId: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export default function TeacherAssignmentsPage() {
|
|
|
|
|
+ const [assignments, setAssignments] = useState<TeacherAssignment[]>([]);
|
|
|
|
|
+ const [teachers, setTeachers] = useState<Teacher[]>([]);
|
|
|
|
|
+ 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 [editingAssignment, setEditingAssignment] = useState<TeacherAssignment | null>(null);
|
|
|
|
|
+ const [formData, setFormData] = useState<CreateAssignmentData>({
|
|
|
|
|
+ teacherId: '',
|
|
|
|
|
+ sectionId: '',
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ fetchAssignments();
|
|
|
|
|
+ fetchTeachers();
|
|
|
|
|
+ fetchSections();
|
|
|
|
|
+ }, []);
|
|
|
|
|
+
|
|
|
|
|
+ const fetchAssignments = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch('/api/admin/teacher-assignments');
|
|
|
|
|
+ if (response.ok) {
|
|
|
|
|
+ const data = await response.json();
|
|
|
|
|
+ setAssignments(data);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ toast.error('Error al cargar las asignaciones');
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Error fetching assignments:', error);
|
|
|
|
|
+ toast.error('Error al cargar las asignaciones');
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setLoading(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const fetchTeachers = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch('/api/admin/users');
|
|
|
|
|
+ if (response.ok) {
|
|
|
|
|
+ const users = await response.json();
|
|
|
|
|
+ const teacherUsers = users.filter((user: any) => user.role === 'TEACHER' && user.teacher);
|
|
|
|
|
+ const teachersData = teacherUsers.map((user: any) => ({
|
|
|
|
|
+ id: user.teacher.id,
|
|
|
|
|
+ firstName: user.teacher.firstName,
|
|
|
|
|
+ lastName: user.teacher.lastName,
|
|
|
|
|
+ cedula: user.teacher.cedula,
|
|
|
|
|
+ email: user.teacher.email,
|
|
|
|
|
+ phone: user.teacher.phone,
|
|
|
|
|
+ }));
|
|
|
|
|
+ setTeachers(teachersData);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Error fetching teachers:', 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 handleCreateAssignment = async () => {
|
|
|
|
|
+ if (!validateForm()) return;
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch('/api/admin/teacher-assignments', {
|
|
|
|
|
+ method: 'POST',
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
|
|
+ },
|
|
|
|
|
+ body: JSON.stringify(formData),
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const result = await response.json();
|
|
|
|
|
+
|
|
|
|
|
+ if (response.ok) {
|
|
|
|
|
+ toast.success('Asignación creada exitosamente');
|
|
|
|
|
+ setIsCreateDialogOpen(false);
|
|
|
|
|
+ resetForm();
|
|
|
|
|
+ fetchAssignments();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ toast.error(result.message || 'Error al crear la asignación');
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Error creating assignment:', error);
|
|
|
|
|
+ toast.error('Error al crear la asignación');
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleUpdateAssignment = async () => {
|
|
|
|
|
+ if (!editingAssignment || !validateForm()) return;
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch(`/api/admin/teacher-assignments/${editingAssignment.id}`, {
|
|
|
|
|
+ method: 'PUT',
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
|
|
+ },
|
|
|
|
|
+ body: JSON.stringify({
|
|
|
|
|
+ ...formData,
|
|
|
|
|
+ isActive: editingAssignment.isActive,
|
|
|
|
|
+ }),
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const result = await response.json();
|
|
|
|
|
+
|
|
|
|
|
+ if (response.ok) {
|
|
|
|
|
+ toast.success('Asignación actualizada exitosamente');
|
|
|
|
|
+ setIsEditDialogOpen(false);
|
|
|
|
|
+ setEditingAssignment(null);
|
|
|
|
|
+ resetForm();
|
|
|
|
|
+ fetchAssignments();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ toast.error(result.message || 'Error al actualizar la asignación');
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Error updating assignment:', error);
|
|
|
|
|
+ toast.error('Error al actualizar la asignación');
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleDeleteAssignment = async (assignmentId: string) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch(`/api/admin/teacher-assignments/${assignmentId}`, {
|
|
|
|
|
+ method: 'DELETE',
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const result = await response.json();
|
|
|
|
|
+
|
|
|
|
|
+ if (response.ok) {
|
|
|
|
|
+ toast.success(result.message || 'Asignación eliminada exitosamente');
|
|
|
|
|
+ fetchAssignments();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ toast.error(result.message || 'Error al eliminar la asignación');
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Error deleting assignment:', error);
|
|
|
|
|
+ toast.error('Error al eliminar la asignación');
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleToggleActive = async (assignment: TeacherAssignment) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch(`/api/admin/teacher-assignments/${assignment.id}`, {
|
|
|
|
|
+ method: 'PUT',
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
|
|
+ },
|
|
|
|
|
+ body: JSON.stringify({
|
|
|
|
|
+ teacherId: assignment.teacherId,
|
|
|
|
|
+ sectionId: assignment.sectionId,
|
|
|
|
|
+ isActive: !assignment.isActive,
|
|
|
|
|
+ }),
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const result = await response.json();
|
|
|
|
|
+
|
|
|
|
|
+ if (response.ok) {
|
|
|
|
|
+ toast.success(`Asignación ${!assignment.isActive ? 'activada' : 'desactivada'} exitosamente`);
|
|
|
|
|
+ fetchAssignments();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ toast.error(result.message || 'Error al cambiar el estado de la asignación');
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Error toggling assignment status:', error);
|
|
|
|
|
+ toast.error('Error al cambiar el estado de la asignación');
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const validateForm = (): boolean => {
|
|
|
|
|
+ if (!formData.teacherId) {
|
|
|
|
|
+ toast.error('Por favor selecciona un profesor');
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!formData.sectionId) {
|
|
|
|
|
+ toast.error('Por favor selecciona una sección');
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ return true;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const resetForm = () => {
|
|
|
|
|
+ setFormData({
|
|
|
|
|
+ teacherId: '',
|
|
|
|
|
+ sectionId: '',
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const openEditDialog = (assignment: TeacherAssignment) => {
|
|
|
|
|
+ setEditingAssignment(assignment);
|
|
|
|
|
+ setFormData({
|
|
|
|
|
+ teacherId: assignment.teacherId,
|
|
|
|
|
+ sectionId: assignment.sectionId,
|
|
|
|
|
+ });
|
|
|
|
|
+ setIsEditDialogOpen(true);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const filteredAssignments = assignments.filter(assignment => {
|
|
|
|
|
+ const searchLower = searchTerm.toLowerCase();
|
|
|
|
|
+ const teacherName = `${assignment.teacher.firstName} ${assignment.teacher.lastName}`.toLowerCase();
|
|
|
|
|
+ const sectionName = assignment.section.name.toLowerCase();
|
|
|
|
|
+ const className = assignment.section.class.name.toLowerCase();
|
|
|
|
|
+ const classCode = assignment.section.class.code.toLowerCase();
|
|
|
|
|
+
|
|
|
|
|
+ return teacherName.includes(searchLower) ||
|
|
|
|
|
+ sectionName.includes(searchLower) ||
|
|
|
|
|
+ className.includes(searchLower) ||
|
|
|
|
|
+ classCode.includes(searchLower);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const getStatusBadgeVariant = (isActive: boolean) => {
|
|
|
|
|
+ return isActive ? 'default' : 'secondary';
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const getTeacherName = (teacher: Teacher) => {
|
|
|
|
|
+ return `${teacher.firstName} ${teacher.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">Asignaciones de Profesores</h1>
|
|
|
|
|
+ <p className="text-muted-foreground">
|
|
|
|
|
+ Gestiona las asignaciones de profesores a secciones
|
|
|
|
|
+ </p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
|
|
|
|
|
+ <DialogTrigger asChild>
|
|
|
|
|
+ <Button onClick={() => resetForm()}>
|
|
|
|
|
+ <Plus className="mr-2 h-4 w-4" />
|
|
|
|
|
+ Nueva Asignación
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </DialogTrigger>
|
|
|
|
|
+ <DialogContent className="sm:max-w-[425px]">
|
|
|
|
|
+ <DialogHeader>
|
|
|
|
|
+ <DialogTitle>Crear Nueva Asignación</DialogTitle>
|
|
|
|
|
+ <DialogDescription>
|
|
|
|
|
+ Asigna un profesor a una sección específica.
|
|
|
|
|
+ </DialogDescription>
|
|
|
|
|
+ </DialogHeader>
|
|
|
|
|
+ <div className="grid gap-4 py-4">
|
|
|
|
|
+ <div className="grid gap-2">
|
|
|
|
|
+ <Label htmlFor="teacher">Profesor</Label>
|
|
|
|
|
+ <Select
|
|
|
|
|
+ value={formData.teacherId}
|
|
|
|
|
+ onValueChange={(value) => setFormData({ ...formData, teacherId: value })}
|
|
|
|
|
+ >
|
|
|
|
|
+ <SelectTrigger>
|
|
|
|
|
+ <SelectValue placeholder="Selecciona un profesor" />
|
|
|
|
|
+ </SelectTrigger>
|
|
|
|
|
+ <SelectContent>
|
|
|
|
|
+ {teachers.map((teacher) => (
|
|
|
|
|
+ <SelectItem key={teacher.id} value={teacher.id}>
|
|
|
|
|
+ {getTeacherName(teacher)} - {teacher.cedula}
|
|
|
|
|
+ </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={handleCreateAssignment}>
|
|
|
|
|
+ Crear Asignación
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </DialogFooter>
|
|
|
|
|
+ </DialogContent>
|
|
|
|
|
+ </Dialog>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <CardTitle className="flex items-center gap-2">
|
|
|
|
|
+ <GraduationCap className="h-5 w-5" />
|
|
|
|
|
+ Asignaciones del Sistema
|
|
|
|
|
+ </CardTitle>
|
|
|
|
|
+ <CardDescription>
|
|
|
|
|
+ Lista de todas las asignaciones de profesores a secciones
|
|
|
|
|
+ </CardDescription>
|
|
|
|
|
+ <div className="flex items-center space-x-2">
|
|
|
|
|
+ <Search className="h-4 w-4 text-muted-foreground" />
|
|
|
|
|
+ <Input
|
|
|
|
|
+ placeholder="Buscar por profesor, sección o materia..."
|
|
|
|
|
+ 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 asignaciones...</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <Table>
|
|
|
|
|
+ <TableHeader>
|
|
|
|
|
+ <TableRow>
|
|
|
|
|
+ <TableHead>Profesor</TableHead>
|
|
|
|
|
+ <TableHead>Cédula</TableHead>
|
|
|
|
|
+ <TableHead>Materia</TableHead>
|
|
|
|
|
+ <TableHead>Sección</TableHead>
|
|
|
|
|
+ <TableHead>Periodo</TableHead>
|
|
|
|
|
+ <TableHead>Estado</TableHead>
|
|
|
|
|
+ <TableHead>Fecha Asignación</TableHead>
|
|
|
|
|
+ <TableHead className="text-right">Acciones</TableHead>
|
|
|
|
|
+ </TableRow>
|
|
|
|
|
+ </TableHeader>
|
|
|
|
|
+ <TableBody>
|
|
|
|
|
+ {filteredAssignments.length === 0 ? (
|
|
|
|
|
+ <TableRow>
|
|
|
|
|
+ <TableCell colSpan={8} className="text-center py-8 text-muted-foreground">
|
|
|
|
|
+ {searchTerm ? 'No se encontraron asignaciones que coincidan con la búsqueda' : 'No hay asignaciones registradas'}
|
|
|
|
|
+ </TableCell>
|
|
|
|
|
+ </TableRow>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ filteredAssignments.map((assignment) => (
|
|
|
|
|
+ <TableRow key={assignment.id}>
|
|
|
|
|
+ <TableCell className="font-medium">
|
|
|
|
|
+ {getTeacherName(assignment.teacher)}
|
|
|
|
|
+ </TableCell>
|
|
|
|
|
+ <TableCell>{assignment.teacher.cedula}</TableCell>
|
|
|
|
|
+ <TableCell>
|
|
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
|
|
+ <BookOpen className="h-4 w-4 text-muted-foreground" />
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <div className="font-medium">{assignment.section.class.name}</div>
|
|
|
|
|
+ <div className="text-sm text-muted-foreground">{assignment.section.class.code}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </TableCell>
|
|
|
|
|
+ <TableCell>{assignment.section.name}</TableCell>
|
|
|
|
|
+ <TableCell>
|
|
|
|
|
+ <Badge variant={assignment.section.class.period.isActive ? 'default' : 'secondary'}>
|
|
|
|
|
+ {assignment.section.class.period.name}
|
|
|
|
|
+ </Badge>
|
|
|
|
|
+ </TableCell>
|
|
|
|
|
+ <TableCell>
|
|
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
|
|
+ <Switch
|
|
|
|
|
+ checked={assignment.isActive}
|
|
|
|
|
+ onCheckedChange={() => handleToggleActive(assignment)}
|
|
|
|
|
+ />
|
|
|
|
|
+ <Badge variant={getStatusBadgeVariant(assignment.isActive)}>
|
|
|
|
|
+ {assignment.isActive ? 'Activa' : 'Inactiva'}
|
|
|
|
|
+ </Badge>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </TableCell>
|
|
|
|
|
+ <TableCell>
|
|
|
|
|
+ {new Date(assignment.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(assignment)}
|
|
|
|
|
+ >
|
|
|
|
|
+ <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 asignación de {getTeacherName(assignment.teacher)} a la sección {assignment.section.name}.
|
|
|
|
|
+ {assignment.isActive && ' Si existen registros de asistencia, la asignación será desactivada en lugar de eliminada.'}
|
|
|
|
|
+ </AlertDialogDescription>
|
|
|
|
|
+ </AlertDialogHeader>
|
|
|
|
|
+ <AlertDialogFooter>
|
|
|
|
|
+ <AlertDialogCancel>Cancelar</AlertDialogCancel>
|
|
|
|
|
+ <AlertDialogAction
|
|
|
|
|
+ onClick={() => handleDeleteAssignment(assignment.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 Asignación</DialogTitle>
|
|
|
|
|
+ <DialogDescription>
|
|
|
|
|
+ Modifica los detalles de la asignación.
|
|
|
|
|
+ </DialogDescription>
|
|
|
|
|
+ </DialogHeader>
|
|
|
|
|
+ <div className="grid gap-4 py-4">
|
|
|
|
|
+ <div className="grid gap-2">
|
|
|
|
|
+ <Label htmlFor="edit-teacher">Profesor</Label>
|
|
|
|
|
+ <Select
|
|
|
|
|
+ value={formData.teacherId}
|
|
|
|
|
+ onValueChange={(value) => setFormData({ ...formData, teacherId: value })}
|
|
|
|
|
+ >
|
|
|
|
|
+ <SelectTrigger>
|
|
|
|
|
+ <SelectValue placeholder="Selecciona un profesor" />
|
|
|
|
|
+ </SelectTrigger>
|
|
|
|
|
+ <SelectContent>
|
|
|
|
|
+ {teachers.map((teacher) => (
|
|
|
|
|
+ <SelectItem key={teacher.id} value={teacher.id}>
|
|
|
|
|
+ {getTeacherName(teacher)} - {teacher.cedula}
|
|
|
|
|
+ </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={handleUpdateAssignment}>
|
|
|
|
|
+ Actualizar Asignación
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </DialogFooter>
|
|
|
|
|
+ </DialogContent>
|
|
|
|
|
+ </Dialog>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </MainLayout>
|
|
|
|
|
+ );
|
|
|
|
|
+}
|