Эх сурвалжийг харах

yeah i forgot components exist oops

Matthew Trejo 4 сар өмнө
parent
commit
93b53d33e8

+ 71 - 53
src/app/admin/classes/page.tsx

@@ -7,8 +7,11 @@ 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 { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
+import { Badge } from '@/components/ui/badge';
 import { Trash2, Edit, Plus, BookOpen } from 'lucide-react';
 import { DashboardLayout } from '@/components/dashboard-layout';
+import { Spinner } from '@/components/ui/spinner';
 
 interface Period {
   id: string;
@@ -270,7 +273,7 @@ export default function ClassesPage() {
 
               <div className="flex gap-2">
                 <Button type="submit" disabled={loading}>
-                  {loading ? 'Procesando...' : (editingId ? 'Actualizar' : 'Crear')}
+                  {loading ? <Spinner size="sm" /> : (editingId ? 'Actualizar' : 'Crear')}
                 </Button>
                 {editingId && (
                   <Button type="button" variant="outline" onClick={handleCancel}>
@@ -300,58 +303,73 @@ export default function ClassesPage() {
             <CardTitle>Clases Registradas ({classes.length})</CardTitle>
           </CardHeader>
           <CardContent>
-            <div className="space-y-3 max-h-96 overflow-y-auto">
-              {classes.length === 0 ? (
-                <p className="text-gray-500 text-center py-4">No hay clases registradas</p>
-              ) : (
-                classes.map((classItem) => (
-                  <div
-                    key={classItem.id}
-                    className="flex items-center justify-between p-3 border rounded-lg hover:bg-gray-50"
-                  >
-                    <div className="flex-1">
-                      <div className="flex items-center gap-2 flex-wrap">
-                        <h3 className="font-medium">{classItem.name}</h3>
-                        <span className="text-sm bg-blue-100 text-blue-800 px-2 py-1 rounded">
-                          {classItem.code}
-                        </span>
-                        <span className="text-sm bg-gray-100 text-gray-800 px-2 py-1 rounded">
-                          {classItem.credits} créditos
-                        </span>
-                        {classItem.period && (
-                          <span className="text-sm bg-green-100 text-green-800 px-2 py-1 rounded">
-                            {classItem.period.name}
-                          </span>
-                        )}
-                      </div>
-                      {classItem.description && (
-                        <p className="text-sm text-gray-600 mt-1">{classItem.description}</p>
-                      )}
-                      <p className="text-xs text-gray-500 mt-1">
-                        Estado: {classItem.isActive ? 'Activa' : 'Inactiva'}
-                      </p>
-                    </div>
-                    <div className="flex gap-2">
-                      <Button
-                        size="sm"
-                        variant="outline"
-                        onClick={() => handleEdit(classItem)}
-                      >
-                        <Edit className="h-4 w-4" />
-                      </Button>
-                      <Button
-                        size="sm"
-                        variant="outline"
-                        onClick={() => handleDelete(classItem.id)}
-                        className="text-red-600 hover:text-red-700"
-                      >
-                        <Trash2 className="h-4 w-4" />
-                      </Button>
-                    </div>
-                  </div>
-                ))
-              )}
-            </div>
+            {classes.length === 0 ? (
+              <p className="text-gray-500 text-center py-8">No hay clases registradas</p>
+            ) : (
+              <div className="max-h-96 overflow-y-auto">
+                <Table>
+                  <TableHeader>
+                    <TableRow>
+                      <TableHead>Clase</TableHead>
+                      <TableHead>Código</TableHead>
+                      <TableHead>Créditos</TableHead>
+                      <TableHead>Período</TableHead>
+                      <TableHead>Estado</TableHead>
+                      <TableHead className="text-right">Acciones</TableHead>
+                    </TableRow>
+                  </TableHeader>
+                  <TableBody>
+                    {classes.map((classItem) => (
+                      <TableRow key={classItem.id}>
+                        <TableCell>
+                          <div>
+                            <div className="font-medium">{classItem.name}</div>
+                            {classItem.description && (
+                              <div className="text-sm text-gray-600">{classItem.description}</div>
+                            )}
+                          </div>
+                        </TableCell>
+                        <TableCell>
+                          <Badge variant="secondary">{classItem.code}</Badge>
+                        </TableCell>
+                        <TableCell>
+                          <Badge variant="outline">{classItem.credits} créditos</Badge>
+                        </TableCell>
+                        <TableCell>
+                          {classItem.period && (
+                            <Badge variant="default">{classItem.period.name}</Badge>
+                          )}
+                        </TableCell>
+                        <TableCell>
+                          <Badge variant={classItem.isActive ? "default" : "outline"}>
+                            {classItem.isActive ? 'Activa' : 'Inactiva'}
+                          </Badge>
+                        </TableCell>
+                        <TableCell className="text-right">
+                          <div className="flex gap-2 justify-end">
+                            <Button
+                              size="sm"
+                              variant="outline"
+                              onClick={() => handleEdit(classItem)}
+                            >
+                              <Edit className="h-4 w-4" />
+                            </Button>
+                            <Button
+                              size="sm"
+                              variant="outline"
+                              onClick={() => handleDelete(classItem.id)}
+                              className="text-red-600 hover:text-red-700"
+                            >
+                              <Trash2 className="h-4 w-4" />
+                            </Button>
+                          </div>
+                        </TableCell>
+                      </TableRow>
+                    ))}
+                  </TableBody>
+                </Table>
+              </div>
+            )}
           </CardContent>
         </Card>
         </div>

+ 2 - 1
src/app/admin/dashboard/page.tsx

@@ -7,6 +7,7 @@ import { Button } from '@/components/ui/button';
 import { Users, GraduationCap, BookOpen, Calendar, FileText, School, Building, Clock } from 'lucide-react';
 import Link from 'next/link';
 import { DashboardLayout } from '@/components/dashboard-layout';
+import { Spinner } from '@/components/ui/spinner'
 
 interface DashboardStats {
   teachers: number;
@@ -101,7 +102,7 @@ export default function AdminDashboard() {
   if (loading) {
     return (
       <div className="min-h-screen flex items-center justify-center">
-        <div className="text-lg">Cargando dashboard...</div>
+        <Spinner size="lg" />
       </div>
     );
   }

+ 5 - 6
src/app/admin/enrollment-history/page.tsx

@@ -8,6 +8,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
 import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
 import { DashboardLayout } from '@/components/dashboard-layout'
 import { Calendar, Users, Download, Filter } from 'lucide-react'
+import { Spinner } from '@/components/ui/spinner'
 
 interface Period {
   id: string
@@ -143,12 +144,10 @@ export default function EnrollmentHistoryPage() {
 
   if (loading) {
     return (
-      <DashboardLayout breadcrumbs={breadcrumbs}>
-        <div className="flex items-center justify-center min-h-[400px]">
-          <div className="text-lg">Cargando historial...</div>
-        </div>
-      </DashboardLayout>
-    )
+      <div className="min-h-screen flex items-center justify-center">
+        <Spinner size="lg" />
+      </div>
+    );
   }
 
   return (

+ 60 - 45
src/app/admin/partials/page.tsx

@@ -9,6 +9,9 @@ import { Alert, AlertDescription } from '@/components/ui/alert';
 import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
 import { Trash2, Edit, Plus, Calendar } from 'lucide-react';
 import { DashboardLayout } from '@/components/dashboard-layout';
+import { Spinner } from '@/components/ui/spinner';
+import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
+import { Badge } from '@/components/ui/badge';
 
 interface Partial {
   id: string;
@@ -250,7 +253,7 @@ export default function PartialsPage() {
 
               <div className="flex gap-2">
                 <Button type="submit" disabled={loading}>
-                  {loading ? 'Procesando...' : (editingId ? 'Actualizar' : 'Crear')}
+                  {loading ? <Spinner size="sm" /> : (editingId ? 'Actualizar' : 'Crear')}
                 </Button>
                 {editingId && (
                   <Button type="button" variant="outline" onClick={handleCancel}>
@@ -280,50 +283,62 @@ export default function PartialsPage() {
             <CardTitle>Parciales Registrados ({partials.length})</CardTitle>
           </CardHeader>
           <CardContent>
-            <div className="space-y-3 max-h-96 overflow-y-auto">
-              {partials.length === 0 ? (
-                <p className="text-gray-500 text-center py-4">No hay parciales registrados</p>
-              ) : (
-                partials.map((partial) => (
-                  <div
-                    key={partial.id}
-                    className="flex items-center justify-between p-3 border rounded-lg hover:bg-gray-50"
-                  >
-                    <div className="flex-1">
-                      <div className="flex items-center gap-2">
-                        <h3 className="font-medium">{partial.name}</h3>
-                        <span className="text-sm bg-blue-100 text-blue-800 px-2 py-1 rounded">
-                          {partial.periodName}
-                        </span>
-                      </div>
-                      <p className="text-sm text-gray-600 mt-1">
-                        {formatDate(partial.startDate)} - {formatDate(partial.endDate)}
-                      </p>
-                      <p className="text-xs text-gray-500 mt-1">
-                        Estado: {partial.isActive ? 'Activo' : 'Inactivo'}
-                      </p>
-                    </div>
-                    <div className="flex gap-2">
-                      <Button
-                        size="sm"
-                        variant="outline"
-                        onClick={() => handleEdit(partial)}
-                      >
-                        <Edit className="h-4 w-4" />
-                      </Button>
-                      <Button
-                        size="sm"
-                        variant="outline"
-                        onClick={() => handleDelete(partial.id)}
-                        className="text-red-600 hover:text-red-700"
-                      >
-                        <Trash2 className="h-4 w-4" />
-                      </Button>
-                    </div>
-                  </div>
-                ))
-              )}
-            </div>
+            {partials.length === 0 ? (
+              <p className="text-gray-500 text-center py-8">No hay parciales registrados</p>
+            ) : (
+              <div className="max-h-96 overflow-y-auto">
+                <Table>
+                  <TableHeader>
+                    <TableRow>
+                      <TableHead>Nombre</TableHead>
+                      <TableHead>Período</TableHead>
+                      <TableHead>Fechas</TableHead>
+                      <TableHead>Estado</TableHead>
+                      <TableHead className="text-right">Acciones</TableHead>
+                    </TableRow>
+                  </TableHeader>
+                  <TableBody>
+                    {partials.map((partial) => (
+                      <TableRow key={partial.id}>
+                        <TableCell className="font-medium">{partial.name}</TableCell>
+                        <TableCell>
+                          <Badge variant="secondary">
+                            {partial.periodName}
+                          </Badge>
+                        </TableCell>
+                        <TableCell className="text-sm text-gray-600">
+                          {formatDate(partial.startDate)} - {formatDate(partial.endDate)}
+                        </TableCell>
+                        <TableCell>
+                          <Badge variant={partial.isActive ? 'default' : 'outline'}>
+                            {partial.isActive ? 'Activo' : 'Inactivo'}
+                          </Badge>
+                        </TableCell>
+                        <TableCell className="text-right">
+                          <div className="flex gap-2 justify-end">
+                            <Button
+                              size="sm"
+                              variant="outline"
+                              onClick={() => handleEdit(partial)}
+                            >
+                              <Edit className="h-4 w-4" />
+                            </Button>
+                            <Button
+                              size="sm"
+                              variant="outline"
+                              onClick={() => handleDelete(partial.id)}
+                              className="text-red-600 hover:text-red-700"
+                            >
+                              <Trash2 className="h-4 w-4" />
+                            </Button>
+                          </div>
+                        </TableCell>
+                      </TableRow>
+                    ))}
+                  </TableBody>
+                </Table>
+              </div>
+            )}
           </CardContent>
         </Card>
         </div>

+ 29 - 30
src/app/admin/periods/page.tsx

@@ -6,9 +6,12 @@ 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 { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
+import { Badge } from '@/components/ui/badge';
 import { Plus, Edit, Trash2, Calendar, ArrowLeft } from 'lucide-react';
 import Link from 'next/link';
 import { DashboardLayout } from '@/components/dashboard-layout';
+import { Spinner } from '@/components/ui/spinner';
 
 interface Period {
   id: string;
@@ -172,7 +175,7 @@ export default function PeriodsPage() {
     return (
       <div className="container mx-auto p-6">
         <div className="flex items-center justify-center h-64">
-          <div className="text-lg">Cargando períodos académicos...</div>
+          <Spinner size="lg" />
         </div>
       </div>
     );
@@ -274,33 +277,29 @@ export default function PeriodsPage() {
             </div>
           ) : (
             <div className="overflow-x-auto">
-              <table className="w-full border-collapse">
-                <thead>
-                  <tr className="border-b">
-                    <th className="text-left p-2">Nombre</th>
-                    <th className="text-left p-2">Fecha de Inicio</th>
-                    <th className="text-left p-2">Fecha de Fin</th>
-                    <th className="text-left p-2">Estado</th>
-                    <th className="text-left p-2">Acciones</th>
-                  </tr>
-                </thead>
-                <tbody>
+              <Table>
+                <TableHeader>
+                  <TableRow>
+                    <TableHead>Nombre</TableHead>
+                    <TableHead>Fecha de Inicio</TableHead>
+                    <TableHead>Fecha de Fin</TableHead>
+                    <TableHead>Estado</TableHead>
+                    <TableHead className="text-right">Acciones</TableHead>
+                  </TableRow>
+                </TableHeader>
+                <TableBody>
                   {periods.map((period) => (
-                    <tr key={period.id} className="border-b hover:bg-gray-50">
-                      <td className="p-2 font-medium">{period.name}</td>
-                      <td className="p-2">{formatDate(period.startDate)}</td>
-                      <td className="p-2">{formatDate(period.endDate)}</td>
-                      <td className="p-2">
-                        <span className={`px-2 py-1 rounded-full text-xs ${
-                          period.isActive 
-                            ? 'bg-green-100 text-green-800' 
-                            : 'bg-red-100 text-red-800'
-                        }`}>
+                    <TableRow key={period.id}>
+                      <TableCell className="font-medium">{period.name}</TableCell>
+                      <TableCell>{formatDate(period.startDate)}</TableCell>
+                      <TableCell>{formatDate(period.endDate)}</TableCell>
+                      <TableCell>
+                        <Badge variant={period.isActive ? "default" : "outline"}>
                           {period.isActive ? 'Activo' : 'Inactivo'}
-                        </span>
-                      </td>
-                      <td className="p-2">
-                        <div className="flex gap-2">
+                        </Badge>
+                      </TableCell>
+                      <TableCell className="text-right">
+                        <div className="flex gap-2 justify-end">
                           <Button
                             size="sm"
                             variant="outline"
@@ -325,11 +324,11 @@ export default function PeriodsPage() {
                             <Trash2 className="h-4 w-4" />
                           </Button>
                         </div>
-                      </td>
-                    </tr>
+                      </TableCell>
+                    </TableRow>
                   ))}
-                </tbody>
-              </table>
+                </TableBody>
+              </Table>
             </div>
           )}
         </CardContent>

+ 89 - 56
src/app/admin/sections/page.tsx

@@ -9,6 +9,9 @@ import { Alert, AlertDescription } from '@/components/ui/alert';
 import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
 import { Trash2, Edit, Plus, Users } from 'lucide-react';
 import { DashboardLayout } from '@/components/dashboard-layout';
+import { Spinner } from '@/components/ui/spinner';
+import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
+import { Badge } from '@/components/ui/badge';
 
 interface Section {
   id: string;
@@ -248,7 +251,7 @@ export default function SectionsPage() {
 
               <div className="flex gap-2">
                 <Button type="submit" disabled={loading}>
-                  {loading ? 'Procesando...' : (editingId ? 'Actualizar' : 'Crear')}
+                  {loading ? <Spinner size="sm" /> : (editingId ? 'Actualizar' : 'Crear')}
                 </Button>
                 {editingId && (
                   <Button type="button" variant="outline" onClick={handleCancel}>
@@ -278,61 +281,91 @@ export default function SectionsPage() {
             <CardTitle>Secciones Registradas ({sections.length})</CardTitle>
           </CardHeader>
           <CardContent>
-            <div className="space-y-3 max-h-96 overflow-y-auto">
-              {sections.length === 0 ? (
-                <p className="text-gray-500 text-center py-4">No hay secciones registradas</p>
-              ) : (
-                sections.map((section) => (
-                  <div
-                    key={section.id}
-                    className="flex items-center justify-between p-3 border rounded-lg hover:bg-gray-50"
-                  >
-                    <div className="flex-1">
-                      <div className="flex items-center gap-2">
-                        <h3 className="font-medium">{section.name}</h3>
-                        <span className="text-sm bg-blue-100 text-blue-800 px-2 py-1 rounded">
-                          {section.classCode}
-                        </span>
-                      </div>
-                      <p className="text-sm text-gray-600 mt-1">
-                        {section.className} - {section.periodName}
-                      </p>
-                      <p className="text-xs text-gray-500 mt-1">
-                        Profesor: {section.teacherName ? `${section.teacherName} (${section.teacherEmail})` : 'Sin asignar'}
-                      </p>
-                      <p className="text-xs text-gray-500 mt-1">
-                        Estudiantes: <span className={`font-medium ${
-                          section.enrolledStudents >= section.maxStudents 
-                            ? 'text-red-600' 
-                            : section.enrolledStudents >= section.maxStudents * 0.8 
-                            ? 'text-yellow-600' 
-                            : 'text-green-600'
-                        }`}>
-                          {section.enrolledStudents}
-                        </span> / {section.maxStudents} | Estado: {section.isActive ? 'Activa' : 'Inactiva'}
-                      </p>
-                    </div>
-                    <div className="flex gap-2">
-                      <Button
-                        size="sm"
-                        variant="outline"
-                        onClick={() => handleEdit(section)}
-                      >
-                        <Edit className="h-4 w-4" />
-                      </Button>
-                      <Button
-                        size="sm"
-                        variant="outline"
-                        onClick={() => handleDelete(section.id)}
-                        className="text-red-600 hover:text-red-700"
-                      >
-                        <Trash2 className="h-4 w-4" />
-                      </Button>
-                    </div>
-                  </div>
-                ))
-              )}
-            </div>
+            {sections.length === 0 ? (
+              <p className="text-gray-500 text-center py-8">No hay secciones registradas</p>
+            ) : (
+              <div className="max-h-96 overflow-y-auto">
+                <Table>
+                  <TableHeader>
+                    <TableRow>
+                      <TableHead>Sección</TableHead>
+                      <TableHead>Clase</TableHead>
+                      <TableHead>Profesor</TableHead>
+                      <TableHead>Estudiantes</TableHead>
+                      <TableHead>Estado</TableHead>
+                      <TableHead className="text-right">Acciones</TableHead>
+                    </TableRow>
+                  </TableHeader>
+                  <TableBody>
+                    {sections.map((section) => (
+                      <TableRow key={section.id}>
+                        <TableCell className="font-medium">
+                          <div className="flex items-center gap-2">
+                            {section.name}
+                            <Badge variant="secondary">
+                              {section.classCode}
+                            </Badge>
+                          </div>
+                        </TableCell>
+                        <TableCell>
+                          <div className="text-sm">
+                            <div className="font-medium">{section.className}</div>
+                            <div className="text-gray-500">{section.periodName}</div>
+                          </div>
+                        </TableCell>
+                        <TableCell className="text-sm">
+                          {section.teacherName ? (
+                            <div>
+                              <div className="font-medium">{section.teacherName}</div>
+                              <div className="text-gray-500">{section.teacherEmail}</div>
+                            </div>
+                          ) : (
+                            <span className="text-gray-500">Sin asignar</span>
+                          )}
+                        </TableCell>
+                        <TableCell>
+                          <div className="flex items-center gap-2">
+                            <Badge variant={
+                              section.enrolledStudents >= section.maxStudents 
+                                ? 'destructive'
+                                : section.enrolledStudents >= section.maxStudents * 0.8 
+                                ? 'outline'
+                                : 'default'
+                            }>
+                              {section.enrolledStudents}/{section.maxStudents}
+                            </Badge>
+                          </div>
+                        </TableCell>
+                        <TableCell>
+                          <Badge variant={section.isActive ? 'default' : 'outline'}>
+                            {section.isActive ? 'Activa' : 'Inactiva'}
+                          </Badge>
+                        </TableCell>
+                        <TableCell className="text-right">
+                          <div className="flex gap-2 justify-end">
+                            <Button
+                              size="sm"
+                              variant="outline"
+                              onClick={() => handleEdit(section)}
+                            >
+                              <Edit className="h-4 w-4" />
+                            </Button>
+                            <Button
+                              size="sm"
+                              variant="outline"
+                              onClick={() => handleDelete(section.id)}
+                              className="text-red-600 hover:text-red-700"
+                            >
+                              <Trash2 className="h-4 w-4" />
+                            </Button>
+                          </div>
+                        </TableCell>
+                      </TableRow>
+                    ))}
+                  </TableBody>
+                </Table>
+              </div>
+            )}
           </CardContent>
         </Card>
         </div>

+ 2 - 1
src/app/admin/student-enrollments/page.tsx

@@ -12,6 +12,7 @@ import { Trash2, Edit, Plus, UserPlus } from 'lucide-react'
 import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
 import { DashboardLayout } from '@/components/dashboard-layout'
 import { toast } from 'sonner'
+import { Spinner } from '@/components/ui/spinner'
 
 interface Student {
   id: string
@@ -318,7 +319,7 @@ export default function StudentEnrollmentsPage() {
                   Cancelar
                 </Button>
                 <Button type="submit" disabled={loading}>
-                  {loading ? 'Procesando...' : (editingEnrollment ? 'Actualizar' : 'Crear')}
+                  {loading ? <Spinner size="sm" /> : (editingEnrollment ? 'Actualizar' : 'Crear')}
                 </Button>
               </div>
             </form>

+ 34 - 33
src/app/admin/students/page.tsx

@@ -6,9 +6,12 @@ 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 { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
+import { Badge } from '@/components/ui/badge';
 import { Plus, Edit, Trash2, Users, ArrowLeft } from 'lucide-react';
 import Link from 'next/link';
 import { DashboardLayout } from '@/components/dashboard-layout';
+import { Spinner } from '@/components/ui/spinner';
 
 interface Student {
   id: string;
@@ -144,7 +147,7 @@ export default function StudentsPage() {
     return (
       <div className="container mx-auto p-6">
         <div className="flex items-center justify-center h-64">
-          <div className="text-lg">Cargando estudiantes...</div>
+          <Spinner size="lg" />
         </div>
       </div>
     );
@@ -262,37 +265,35 @@ export default function StudentsPage() {
             </div>
           ) : (
             <div className="overflow-x-auto">
-              <table className="w-full border-collapse">
-                <thead>
-                  <tr className="border-b">
-                    <th className="text-left p-2">Nombre</th>
-                    <th className="text-left p-2">Email</th>
-                    <th className="text-left p-2">Cédula</th>
-                    <th className="text-left p-2">Teléfono</th>
-                    <th className="text-left p-2">Estado</th>
-                    <th className="text-left p-2">Acciones</th>
-                  </tr>
-                </thead>
-                <tbody>
+              <Table>
+                <TableHeader>
+                  <TableRow>
+                    <TableHead>Nombre</TableHead>
+                    <TableHead>Email</TableHead>
+                    <TableHead>Cédula</TableHead>
+                    <TableHead>Teléfono</TableHead>
+                    <TableHead>Estado</TableHead>
+                    <TableHead className="text-right">Acciones</TableHead>
+                  </TableRow>
+                </TableHeader>
+                <TableBody>
                   {students.map((student) => (
-                    <tr key={student.id} className="border-b hover:bg-gray-50">
-                      <td className="p-2">
+                    <TableRow key={student.id}>
+                      <TableCell className="font-medium">
                         {student.firstName} {student.lastName}
-                      </td>
-                      <td className="p-2">{student.email}</td>
-                      <td className="p-2">{student.cedula}</td>
-                      <td className="p-2">{student.phone}</td>
-                      <td className="p-2">
-                        <span className={`px-2 py-1 rounded-full text-xs ${
-                          student.isActive 
-                            ? 'bg-green-100 text-green-800' 
-                            : 'bg-red-100 text-red-800'
-                        }`}>
+                      </TableCell>
+                      <TableCell>{student.email}</TableCell>
+                      <TableCell>
+                        <Badge variant="secondary">{student.cedula}</Badge>
+                      </TableCell>
+                      <TableCell>{student.phone}</TableCell>
+                      <TableCell>
+                        <Badge variant={student.isActive ? "default" : "outline"}>
                           {student.isActive ? 'Activo' : 'Inactivo'}
-                        </span>
-                      </td>
-                      <td className="p-2">
-                        <div className="flex gap-2">
+                        </Badge>
+                      </TableCell>
+                      <TableCell className="text-right">
+                        <div className="flex gap-2 justify-end">
                           <Button
                             size="sm"
                             variant="outline"
@@ -309,11 +310,11 @@ export default function StudentsPage() {
                             <Trash2 className="h-4 w-4" />
                           </Button>
                         </div>
-                      </td>
-                    </tr>
+                      </TableCell>
+                    </TableRow>
                   ))}
-                </tbody>
-              </table>
+                </TableBody>
+              </Table>
             </div>
           )}
         </CardContent>

+ 2 - 1
src/app/admin/teacher-assignments/page.tsx

@@ -9,6 +9,7 @@ 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';
+import { Spinner } from '@/components/ui/spinner';
 
 interface TeacherAssignment {
   id: string;
@@ -310,7 +311,7 @@ export default function TeacherAssignmentsPage() {
 
                 <div className="flex gap-2">
                   <Button type="submit" disabled={loading}>
-                    {loading ? 'Procesando...' : (editingId ? 'Actualizar' : 'Crear')}
+                    {loading ? <Spinner size="sm" /> : (editingId ? 'Actualizar' : 'Crear')}
                   </Button>
                   {editingId && (
                     <Button type="button" variant="outline" onClick={handleCancel}>

+ 38 - 35
src/app/admin/teachers/page.tsx

@@ -6,9 +6,12 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
 import { Input } from '@/components/ui/input';
 import { Label } from '@/components/ui/label';
 import { Alert, AlertDescription } from '@/components/ui/alert';
-import { Plus, Edit, Trash2, ArrowLeft } from 'lucide-react';
+import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
+import { Badge } from '@/components/ui/badge';
+import { Plus, Edit, Trash2, ArrowLeft, GraduationCap } from 'lucide-react';
 import Link from 'next/link';
 import { DashboardLayout } from '@/components/dashboard-layout';
+import { Spinner } from '@/components/ui/spinner';
 
 interface Teacher {
   id: string;
@@ -147,7 +150,7 @@ export default function TeachersPage() {
   if (loading) {
     return (
       <div className="min-h-screen flex items-center justify-center">
-        <div className="text-lg">Cargando profesores...</div>
+        <Spinner size="lg" />
       </div>
     );
   }
@@ -161,7 +164,8 @@ export default function TeachersPage() {
     <DashboardLayout breadcrumbs={breadcrumbs}>
       <div className="space-y-6">
         <div className="flex justify-between items-center">
-          <h1 className="text-3xl font-bold">
+          <h1 className="text-3xl font-bold flex items-center gap-2">
+            <GraduationCap className="h-8 w-8" />
             Gestión de Profesores
           </h1>
           <Button onClick={() => setShowForm(true)}>
@@ -278,37 +282,35 @@ export default function TeachersPage() {
           </CardHeader>
           <CardContent>
             <div className="overflow-x-auto">
-              <table className="w-full border-collapse">
-                <thead>
-                  <tr className="border-b">
-                    <th className="text-left p-4">Nombre</th>
-                    <th className="text-left p-4">Email</th>
-                    <th className="text-left p-4">Cédula</th>
-                    <th className="text-left p-4">Teléfono</th>
-                    <th className="text-left p-4">Estado</th>
-                    <th className="text-left p-4">Acciones</th>
-                  </tr>
-                </thead>
-                <tbody>
+              <Table>
+                <TableHeader>
+                  <TableRow>
+                    <TableHead>Nombre</TableHead>
+                    <TableHead>Email</TableHead>
+                    <TableHead>Cédula</TableHead>
+                    <TableHead>Teléfono</TableHead>
+                    <TableHead>Estado</TableHead>
+                    <TableHead className="text-right">Acciones</TableHead>
+                  </TableRow>
+                </TableHeader>
+                <TableBody>
                   {teachers.map((teacher) => (
-                    <tr key={teacher.id} className="border-b hover:bg-gray-50">
-                      <td className="p-4">
+                    <TableRow key={teacher.id}>
+                      <TableCell className="font-medium">
                         {teacher.firstName} {teacher.lastName}
-                      </td>
-                      <td className="p-4">{teacher.email}</td>
-                      <td className="p-4">{teacher.cedula}</td>
-                      <td className="p-4">{teacher.phone}</td>
-                      <td className="p-4">
-                        <span className={`px-2 py-1 rounded-full text-xs ${
-                          teacher.isActive 
-                            ? 'bg-green-100 text-green-800' 
-                            : 'bg-red-100 text-red-800'
-                        }`}>
+                      </TableCell>
+                      <TableCell>{teacher.email}</TableCell>
+                      <TableCell>
+                        <Badge variant="secondary">{teacher.cedula}</Badge>
+                      </TableCell>
+                      <TableCell>{teacher.phone}</TableCell>
+                      <TableCell>
+                        <Badge variant={teacher.isActive ? "default" : "outline"}>
                           {teacher.isActive ? 'Activo' : 'Inactivo'}
-                        </span>
-                      </td>
-                      <td className="p-4">
-                        <div className="flex space-x-2">
+                        </Badge>
+                      </TableCell>
+                      <TableCell className="text-right">
+                        <div className="flex space-x-2 justify-end">
                           <Button
                             size="sm"
                             variant="outline"
@@ -320,15 +322,16 @@ export default function TeachersPage() {
                             size="sm"
                             variant="outline"
                             onClick={() => handleDelete(teacher.id)}
+                            className="text-red-600 hover:text-red-700"
                           >
                             <Trash2 className="h-4 w-4" />
                           </Button>
                         </div>
-                      </td>
-                    </tr>
+                      </TableCell>
+                    </TableRow>
                   ))}
-                </tbody>
-              </table>
+                </TableBody>
+              </Table>
               {teachers.length === 0 && (
                 <div className="text-center py-8 text-gray-500">
                   No hay profesores registrados

+ 2 - 1
src/app/auth/signin/page.tsx

@@ -8,6 +8,7 @@ import { Input } from '@/components/ui/input';
 import { Label } from '@/components/ui/label';
 import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
 import { Alert, AlertDescription } from '@/components/ui/alert';
+import { Spinner } from '@/components/ui/spinner';
 
 export default function SignIn() {
   const [email, setEmail] = useState('');
@@ -90,7 +91,7 @@ export default function SignIn() {
               </Alert>
             )}
             <Button type="submit" className="w-full" disabled={loading}>
-              {loading ? 'Iniciando sesión...' : 'Iniciar Sesión'}
+              {loading ? <Spinner size="sm" /> : 'Iniciar Sesión'}
             </Button>
           </form>
         </CardContent>

+ 3 - 2
src/app/page.tsx

@@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation';
 import { useEffect } from 'react';
 import { Button } from '@/components/ui/button';
 import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
+import { Spinner } from '@/components/ui/spinner';
 
 export default function Home() {
   const { data: session, status } = useSession();
@@ -34,7 +35,7 @@ export default function Home() {
   if (status === 'loading') {
     return (
       <div className="min-h-screen flex items-center justify-center">
-        <div className="text-lg">Cargando...</div>
+        <Spinner size="lg" />
       </div>
     );
   }
@@ -66,7 +67,7 @@ export default function Home() {
 
   return (
     <div className="min-h-screen flex items-center justify-center">
-      <div className="text-lg">Redirigiendo...</div>
+      <Spinner size="lg" />
     </div>
   );
 }

+ 2 - 4
src/app/student/dashboard/page.tsx

@@ -7,6 +7,7 @@ import { Badge } from '@/components/ui/badge'
 import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from '@/components/ui/breadcrumb'
 import { BookOpen, Calendar, Users, CheckCircle, XCircle, Clock } from 'lucide-react'
 import { toast } from 'sonner'
+import { Spinner } from '@/components/ui/spinner'
 
 interface Section {
   id: string
@@ -102,10 +103,7 @@ export default function StudentDashboard() {
     return (
       <DashboardLayout>
         <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>
+          <Spinner size="lg" />
         </div>
       </DashboardLayout>
     )

+ 2 - 1
src/app/student/page.tsx

@@ -5,6 +5,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
 import { Badge } from '@/components/ui/badge'
 import { DashboardLayout } from '@/components/dashboard-layout'
 import { BookOpen, Calendar, Users, GraduationCap } from 'lucide-react'
+import { Spinner } from '@/components/ui/spinner'
 
 interface EnrolledSection {
   id: string
@@ -55,7 +56,7 @@ export default function StudentPage() {
     return (
       <DashboardLayout breadcrumbs={breadcrumbs}>
         <div className="flex items-center justify-center min-h-[400px]">
-          <div className="text-lg">Cargando secciones...</div>
+          <Spinner size="lg" />
         </div>
       </DashboardLayout>
     )

+ 8 - 1
src/app/teacher/attendance-history/page.tsx

@@ -11,6 +11,7 @@ import { Badge } from '@/components/ui/badge'
 import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
 import { Calendar, Users, Clock, UserX, UserCheck } from 'lucide-react'
 import { toast } from 'sonner'
+import { Spinner } from '@/components/ui/spinner'
 
 interface Section {
   id: string
@@ -201,7 +202,13 @@ export default function AttendanceHistoryPage() {
                   disabled={loading || !selectedSection}
                   className="w-full"
                 >
-                  {loading ? 'Cargando...' : 'Buscar Historial'}
+                  {loading ? (
+                    <div className="flex items-center gap-2">
+                      <Spinner size="sm" />
+                    </div>
+                  ) : (
+                    'Buscar Historial'
+                  )}
                 </Button>
               </div>
             </div>

+ 9 - 2
src/app/teacher/attendance/page.tsx

@@ -8,6 +8,7 @@ import { Badge } from '@/components/ui/badge'
 import { CheckCircle, XCircle, Clock, Users } from 'lucide-react'
 import { toast } from 'sonner'
 import { DashboardLayout } from '@/components/dashboard-layout'
+import { Spinner } from '@/components/ui/spinner'
 
 interface Section {
   id: string
@@ -250,7 +251,13 @@ export default function AttendancePage() {
                 disabled={!selectedSection || !selectedPartial || loading}
                 className="w-full"
               >
-                {loading ? 'Cargando...' : 'Cargar Estudiantes'}
+                {loading ? (
+                  <div className="flex items-center gap-2">
+                    <Spinner size="sm" />
+                  </div>
+                ) : (
+                  'Cargar Estudiantes'
+                )}
               </Button>
             </div>
           </div>
@@ -264,7 +271,7 @@ export default function AttendancePage() {
             <div className="flex justify-between items-center">
               <CardTitle>Lista de Estudiantes</CardTitle>
               <Button onClick={saveAttendance} disabled={saving}>
-                {saving ? 'Guardando...' : 'Guardar Asistencia'}
+                {saving ? <Spinner size="sm" /> : 'Guardar Asistencia'}
               </Button>
             </div>
           </CardHeader>

+ 3 - 8
src/app/teacher/export-reports/page.tsx

@@ -12,6 +12,7 @@ import { Download, FileText, Calendar, Users } from 'lucide-react'
 import { format } from 'date-fns'
 import { es } from 'date-fns/locale'
 import { toast } from 'sonner'
+import { Spinner } from '@/components/ui/spinner'
 
 interface Section {
   id: number
@@ -121,10 +122,7 @@ export default function ExportReportsPage() {
   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>
+        <Spinner size="lg" />
       </div>
     )
   }
@@ -208,10 +206,7 @@ export default function ExportReportsPage() {
               className="w-full"
             >
               {isExporting ? (
-                <>
-                  <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
-                  Exportando...
-                </>
+                <Spinner size="sm" />
               ) : (
                 <>
                   <Download className="h-4 w-4 mr-2" />

+ 4 - 1
src/app/teacher/student-attendance/page.tsx

@@ -21,6 +21,7 @@ import {
 } from '@/components/ui/card'
 import { Badge } from '@/components/ui/badge'
 import { Search, User, Calendar, BookOpen, Users } from 'lucide-react'
+import { Spinner } from '@/components/ui/spinner'
 import { format } from 'date-fns'
 import { es } from 'date-fns/locale'
 
@@ -221,7 +222,9 @@ export default function StudentAttendancePage() {
 
             {/* Search Results */}
             {searching && (
-              <div className="text-sm text-muted-foreground">Buscando estudiantes...</div>
+              <div className="flex items-center gap-2 text-sm text-muted-foreground">
+                <Spinner size="sm" />
+              </div>
             )}
 
             {searchResults.length > 0 && (

+ 21 - 11
src/components/ui/badge.tsx

@@ -1,20 +1,22 @@
 import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
 import { cva, type VariantProps } from "class-variance-authority"
 
 import { cn } from "@/lib/utils"
 
 const badgeVariants = cva(
-  "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
+  "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
   {
     variants: {
       variant: {
         default:
-          "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
+          "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
         secondary:
-          "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
+          "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
         destructive:
-          "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
-        outline: "text-foreground",
+          "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+        outline:
+          "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
       },
     },
     defaultVariants: {
@@ -23,14 +25,22 @@ const badgeVariants = cva(
   }
 )
 
-export interface BadgeProps
-  extends React.HTMLAttributes<HTMLDivElement>,
-    VariantProps<typeof badgeVariants> {}
+function Badge({
+  className,
+  variant,
+  asChild = false,
+  ...props
+}: React.ComponentProps<"span"> &
+  VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
+  const Comp = asChild ? Slot : "span"
 
-function Badge({ className, variant, ...props }: BadgeProps) {
   return (
-    <div className={cn(badgeVariants({ variant }), className)} {...props} />
+    <Comp
+      data-slot="badge"
+      className={cn(badgeVariants({ variant }), className)}
+      {...props}
+    />
   )
 }
 
-export { Badge, badgeVariants }
+export { Badge, badgeVariants }

+ 46 - 0
src/components/ui/spinner.tsx

@@ -0,0 +1,46 @@
+import { cn } from "@/lib/utils"
+import { cva, type VariantProps } from "class-variance-authority"
+
+const spinnerVariants = cva(
+  "animate-spin rounded-full border-2 border-solid border-current border-r-transparent",
+  {
+    variants: {
+      size: {
+        sm: "h-4 w-4",
+        default: "h-6 w-6",
+        lg: "h-8 w-8",
+        xl: "h-12 w-12",
+      },
+      variant: {
+        default: "text-primary",
+        secondary: "text-secondary-foreground",
+        muted: "text-muted-foreground",
+      },
+    },
+    defaultVariants: {
+      size: "default",
+      variant: "default",
+    },
+  }
+)
+
+export interface SpinnerProps
+  extends React.HTMLAttributes<HTMLDivElement>,
+    VariantProps<typeof spinnerVariants> {
+  label?: string
+}
+
+const Spinner = ({ className, size, variant, label, ...props }: SpinnerProps) => {
+  return (
+    <div
+      className={cn(spinnerVariants({ size, variant }), className)}
+      role="status"
+      aria-label={label || "Cargando"}
+      {...props}
+    >
+      <span className="sr-only">{label || "Cargando..."}</span>
+    </div>
+  )
+}
+
+export { Spinner, spinnerVariants }