| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322 |
- 'use client'
- import { useState, useEffect } from 'react'
- import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
- import { Button } from '@/components/ui/button'
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
- import { Badge } from '@/components/ui/badge'
- import { CheckCircle, XCircle, Clock, Users } from 'lucide-react'
- import { toast } from 'sonner'
- interface Section {
- id: string
- name: string
- className: string
- periodName: string
- studentCount: number
- isActive: boolean
- }
- interface Partial {
- id: string
- name: string
- startDate: string
- endDate: string
- isActive: boolean
- }
- interface Student {
- id: string
- name: string
- email: string
- attendance?: {
- id: string
- status: 'present' | 'absent' | 'late'
- date: string
- }
- }
- export default function AttendancePage() {
- const [sections, setSections] = useState<Section[]>([])
- const [partials, setPartials] = useState<Partial[]>([])
- const [students, setStudents] = useState<Student[]>([])
- const [selectedSection, setSelectedSection] = useState<string>('')
- const [selectedPartial, setSelectedPartial] = useState<string>('')
- const [selectedDate, setSelectedDate] = useState<string>(new Date().toISOString().split('T')[0])
- const [loading, setLoading] = useState(false)
- const [saving, setSaving] = useState(false)
- useEffect(() => {
- fetchSections()
- fetchPartials()
- }, [])
- useEffect(() => {
- if (selectedSection && selectedPartial && selectedDate) {
- fetchStudents()
- }
- }, [selectedSection, selectedPartial, selectedDate])
- const fetchSections = async () => {
- try {
- const response = await fetch('/api/teacher/sections')
- if (response.ok) {
- const data = await response.json()
- setSections(data.filter((s: Section) => s.isActive))
- }
- } catch (error) {
- toast.error('Error al cargar las secciones')
- }
- }
- const fetchPartials = async () => {
- try {
- const response = await fetch('/api/admin/partials')
- if (response.ok) {
- const data = await response.json()
- setPartials(data.filter((p: Partial) => p.isActive))
- }
- } catch (error) {
- toast.error('Error al cargar los parciales')
- }
- }
- const fetchStudents = async () => {
- if (!selectedSection || !selectedPartial || !selectedDate) return
-
- setLoading(true)
- try {
- const response = await fetch(
- `/api/teacher/attendance?sectionId=${selectedSection}&partialId=${selectedPartial}&date=${selectedDate}`
- )
- if (response.ok) {
- const data = await response.json()
- setStudents(data)
- }
- } catch (error) {
- toast.error('Error al cargar los estudiantes')
- } finally {
- setLoading(false)
- }
- }
- const updateAttendance = (studentId: string, status: 'present' | 'absent' | 'late') => {
- setStudents(prev => prev.map(student =>
- student.id === studentId
- ? {
- ...student,
- attendance: {
- id: student.attendance?.id || '',
- status,
- date: selectedDate
- }
- }
- : student
- ))
- }
- const saveAttendance = async () => {
- if (!selectedSection || !selectedPartial || !selectedDate) {
- toast.error('Selecciona sección, parcial y fecha')
- return
- }
- setSaving(true)
- try {
- const attendanceData = students.map(student => ({
- studentId: student.id,
- sectionId: selectedSection,
- partialId: selectedPartial,
- date: selectedDate,
- status: student.attendance?.status || 'absent'
- }))
- const response = await fetch('/api/teacher/attendance', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({ attendance: attendanceData })
- })
- if (response.ok) {
- toast.success('Asistencia guardada correctamente')
- fetchStudents() // Refresh data
- } else {
- toast.error('Error al guardar la asistencia')
- }
- } catch (error) {
- toast.error('Error al guardar la asistencia')
- } finally {
- setSaving(false)
- }
- }
- const getStatusIcon = (status: string) => {
- switch (status) {
- case 'present':
- return <CheckCircle className="h-4 w-4 text-green-600" />
- case 'late':
- return <Clock className="h-4 w-4 text-yellow-600" />
- case 'absent':
- return <XCircle className="h-4 w-4 text-red-600" />
- default:
- return <XCircle className="h-4 w-4 text-gray-400" />
- }
- }
- const getStatusBadge = (status: string) => {
- switch (status) {
- case 'present':
- return <Badge className="bg-green-100 text-green-800">Presente</Badge>
- case 'late':
- return <Badge className="bg-yellow-100 text-yellow-800">Tardanza</Badge>
- case 'absent':
- return <Badge className="bg-red-100 text-red-800">Ausente</Badge>
- default:
- return <Badge variant="secondary">Sin marcar</Badge>
- }
- }
- return (
- <div className="space-y-6">
- <div>
- <h1 className="text-2xl font-bold text-gray-900">Gestión de Asistencia</h1>
- <p className="text-gray-600">Registra la asistencia de tus estudiantes</p>
- </div>
- {/* Filters */}
- <Card>
- <CardHeader>
- <CardTitle className="flex items-center gap-2">
- <Users className="h-5 w-5" />
- Seleccionar Clase
- </CardTitle>
- </CardHeader>
- <CardContent>
- <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
- <div>
- <label className="block text-sm font-medium mb-2">Sección</label>
- <Select value={selectedSection} onValueChange={setSelectedSection}>
- <SelectTrigger>
- <SelectValue placeholder="Seleccionar sección" />
- </SelectTrigger>
- <SelectContent>
- {sections.map((section) => (
- <SelectItem key={section.id} value={section.id}>
- {section.className} - {section.name}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- </div>
- <div>
- <label className="block text-sm font-medium mb-2">Parcial</label>
- <Select value={selectedPartial} onValueChange={setSelectedPartial}>
- <SelectTrigger>
- <SelectValue placeholder="Seleccionar parcial" />
- </SelectTrigger>
- <SelectContent>
- {partials.map((partial) => (
- <SelectItem key={partial.id} value={partial.id}>
- {partial.name}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- </div>
- <div>
- <label className="block text-sm font-medium mb-2">Fecha</label>
- <input
- type="date"
- value={selectedDate}
- onChange={(e) => setSelectedDate(e.target.value)}
- className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
- />
- </div>
- <div className="flex items-end">
- <Button
- onClick={fetchStudents}
- disabled={!selectedSection || !selectedPartial || loading}
- className="w-full"
- >
- {loading ? 'Cargando...' : 'Cargar Estudiantes'}
- </Button>
- </div>
- </div>
- </CardContent>
- </Card>
- {/* Students List */}
- {students.length > 0 && (
- <Card>
- <CardHeader>
- <div className="flex justify-between items-center">
- <CardTitle>Lista de Estudiantes</CardTitle>
- <Button onClick={saveAttendance} disabled={saving}>
- {saving ? 'Guardando...' : 'Guardar Asistencia'}
- </Button>
- </div>
- </CardHeader>
- <CardContent>
- <div className="space-y-4">
- {students.map((student) => (
- <div
- key={student.id}
- className="flex items-center justify-between p-4 border rounded-lg hover:bg-gray-50"
- >
- <div className="flex items-center space-x-3">
- {getStatusIcon(student.attendance?.status || 'absent')}
- <div>
- <h3 className="font-medium">{student.name}</h3>
- <p className="text-sm text-gray-600">{student.email}</p>
- </div>
- </div>
-
- <div className="flex items-center space-x-4">
- {getStatusBadge(student.attendance?.status || 'absent')}
-
- <div className="flex space-x-2">
- <Button
- size="sm"
- variant={student.attendance?.status === 'present' ? 'default' : 'outline'}
- onClick={() => updateAttendance(student.id, 'present')}
- >
- Presente
- </Button>
- <Button
- size="sm"
- variant={student.attendance?.status === 'late' ? 'default' : 'outline'}
- onClick={() => updateAttendance(student.id, 'late')}
- >
- Tardanza
- </Button>
- <Button
- size="sm"
- variant={student.attendance?.status === 'absent' ? 'default' : 'outline'}
- onClick={() => updateAttendance(student.id, 'absent')}
- >
- Ausente
- </Button>
- </div>
- </div>
- </div>
- ))}
- </div>
- </CardContent>
- </Card>
- )}
- {selectedSection && selectedPartial && students.length === 0 && !loading && (
- <Card>
- <CardContent className="text-center py-8">
- <p className="text-gray-500">No hay estudiantes matriculados en esta sección.</p>
- </CardContent>
- </Card>
- )}
- </div>
- )
- }
|