| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- 'use client'
- import { useState, useEffect } from 'react'
- import { DashboardLayout } from '@/components/dashboard-layout'
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
- import { Button } from '@/components/ui/button'
- import { Input } from '@/components/ui/input'
- import { Label } from '@/components/ui/label'
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
- import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from '@/components/ui/breadcrumb'
- import { Download, FileText, Calendar, Users } from 'lucide-react'
- import { format } from 'date-fns'
- import { es } from 'date-fns/locale'
- import { toast } from 'sonner'
- interface Section {
- id: number
- name: string
- className: string
- classCode: string
- }
- export default function ExportReportsPage() {
- const [sections, setSections] = useState<Section[]>([])
- const [selectedDate, setSelectedDate] = useState('')
- const [selectedSection, setSelectedSection] = useState('all')
- const [isExporting, setIsExporting] = useState(false)
- const [loading, setLoading] = useState(true)
- useEffect(() => {
- fetchSections()
- // Establecer fecha actual por defecto
- const today = new Date()
- setSelectedDate(format(today, 'yyyy-MM-dd'))
- }, [])
- const fetchSections = async () => {
- try {
- const response = await fetch('/api/teacher/sections')
- if (response.ok) {
- const data = await response.json()
- setSections(data)
- } else {
- toast.error('Error al cargar las secciones')
- }
- } catch (error) {
- console.error('Error:', error)
- toast.error('Error al cargar las secciones')
- } finally {
- setLoading(false)
- }
- }
- const handleExport = async () => {
- if (!selectedDate) {
- toast.error('Por favor selecciona una fecha')
- return
- }
- setIsExporting(true)
- try {
- const params = new URLSearchParams({
- date: selectedDate,
- sectionId: selectedSection
- })
- const response = await fetch(`/api/teacher/export-attendance?${params}`)
-
- if (!response.ok) {
- const errorData = await response.json()
- throw new Error(errorData.error || 'Error al exportar')
- }
- // Obtener el blob del archivo CSV
- const blob = await response.blob()
-
- // Crear URL para descarga
- const url = window.URL.createObjectURL(blob)
- const link = document.createElement('a')
- link.href = url
-
- // Obtener nombre del archivo desde los headers o generar uno
- const contentDisposition = response.headers.get('Content-Disposition')
- let filename = `asistencia_${selectedDate}.csv`
-
- if (contentDisposition) {
- const filenameMatch = contentDisposition.match(/filename="(.+)"/)
- if (filenameMatch) {
- filename = filenameMatch[1]
- }
- }
-
- link.download = filename
- document.body.appendChild(link)
- link.click()
-
- // Limpiar
- document.body.removeChild(link)
- window.URL.revokeObjectURL(url)
-
- toast.success('Reporte exportado exitosamente')
- } catch (error) {
- console.error('Error al exportar:', error)
- toast.error(error instanceof Error ? error.message : 'Error al exportar el reporte')
- } finally {
- setIsExporting(false)
- }
- }
- const getSelectedSectionName = () => {
- if (selectedSection === 'all') return 'Todas las secciones'
- const section = sections.find(s => s.id.toString() === selectedSection)
- return section ? `${section.className} - ${section.name}` : 'Sección desconocida'
- }
- const formatSelectedDate = () => {
- if (!selectedDate) return ''
- return format(new Date(selectedDate), 'dd \\de MMMM \\de yyyy', { locale: es })
- }
- if (loading) {
- return (
- <div className="flex items-center justify-center min-h-[400px]">
- <div className="text-center">
- <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
- <p className="text-muted-foreground">Cargando...</p>
- </div>
- </div>
- )
- }
- return (
- <DashboardLayout>
- <div className="space-y-6">
- {/* Breadcrumb */}
- <Breadcrumb>
- <BreadcrumbList>
- <BreadcrumbItem>
- <BreadcrumbLink href="/teacher">Profesor</BreadcrumbLink>
- </BreadcrumbItem>
- <BreadcrumbSeparator />
- <BreadcrumbItem>
- <BreadcrumbPage>Exportar Reportes</BreadcrumbPage>
- </BreadcrumbItem>
- </BreadcrumbList>
- </Breadcrumb>
- {/* Header */}
- <div className="flex items-center gap-3">
- <div className="p-2 bg-primary/10 rounded-lg">
- <Download className="h-6 w-6 text-primary" />
- </div>
- <div>
- <h1 className="text-2xl font-bold">Exportar Reportes de Asistencia</h1>
- <p className="text-muted-foreground">
- Genera y descarga reportes de asistencia en formato CSV
- </p>
- </div>
- </div>
- <div className="grid gap-6 md:grid-cols-2">
- {/* Configuración de Exportación */}
- <Card>
- <CardHeader>
- <CardTitle className="flex items-center gap-2">
- <Calendar className="h-5 w-5" />
- Configuración del Reporte
- </CardTitle>
- <CardDescription>
- Selecciona la fecha y sección para exportar
- </CardDescription>
- </CardHeader>
- <CardContent className="space-y-4">
- {/* Selector de Fecha */}
- <div className="space-y-2">
- <Label htmlFor="date">Fecha</Label>
- <Input
- id="date"
- type="date"
- value={selectedDate}
- onChange={(e) => setSelectedDate(e.target.value)}
- max={format(new Date(), 'yyyy-MM-dd')}
- />
- </div>
- {/* Selector de Sección */}
- <div className="space-y-2">
- <Label htmlFor="section">Sección</Label>
- <Select value={selectedSection} onValueChange={setSelectedSection}>
- <SelectTrigger>
- <SelectValue placeholder="Selecciona una sección" />
- </SelectTrigger>
- <SelectContent>
- <SelectItem value="all">Todas las secciones</SelectItem>
- {sections.map((section) => (
- <SelectItem key={section.id} value={section.id.toString()}>
- {section.className} - {section.name}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- </div>
- {/* Botón de Exportación */}
- <Button
- onClick={handleExport}
- disabled={!selectedDate || isExporting}
- className="w-full"
- >
- {isExporting ? (
- <>
- <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
- Exportando...
- </>
- ) : (
- <>
- <Download className="h-4 w-4 mr-2" />
- Exportar CSV
- </>
- )}
- </Button>
- </CardContent>
- </Card>
- {/* Vista Previa de Configuración */}
- <Card>
- <CardHeader>
- <CardTitle className="flex items-center gap-2">
- <FileText className="h-5 w-5" />
- Vista Previa del Reporte
- </CardTitle>
- <CardDescription>
- Información que se incluirá en el reporte
- </CardDescription>
- </CardHeader>
- <CardContent className="space-y-4">
- {/* Información del Reporte */}
- <div className="space-y-3">
- <div className="flex justify-between items-center py-2 border-b">
- <span className="font-medium">Fecha:</span>
- <span className="text-muted-foreground">
- {selectedDate ? formatSelectedDate() : 'No seleccionada'}
- </span>
- </div>
-
- <div className="flex justify-between items-center py-2 border-b">
- <span className="font-medium">Sección:</span>
- <span className="text-muted-foreground">
- {getSelectedSectionName()}
- </span>
- </div>
- </div>
- {/* Columnas del CSV */}
- <div className="space-y-2">
- <h4 className="font-medium text-sm">Columnas incluidas:</h4>
- <div className="grid grid-cols-2 gap-2 text-sm text-muted-foreground">
- <div>• Fecha</div>
- <div>• Estudiante</div>
- <div>• Email</div>
- <div>• Clase</div>
- <div>• Código de Clase</div>
- <div>• Sección</div>
- <div>• Estado</div>
- <div>• Parcial</div>
- <div>• Razón</div>
- </div>
- </div>
- {/* Información adicional */}
- <div className="bg-muted/50 p-3 rounded-lg">
- <div className="flex items-start gap-2">
- <Users className="h-4 w-4 mt-0.5 text-muted-foreground" />
- <div className="text-sm text-muted-foreground">
- <p className="font-medium mb-1">Formato CSV</p>
- <p>El archivo incluirá todos los registros de asistencia del día seleccionado, ordenados por fecha y nombre del estudiante.</p>
- </div>
- </div>
- </div>
- </CardContent>
- </Card>
- </div>
- </div>
- </DashboardLayout>
- )
- }
|