page.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. 'use client';
  2. import { useState, useEffect } from 'react';
  3. import { Button } from '@/components/ui/button';
  4. import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
  5. import { Input } from '@/components/ui/input';
  6. import { Label } from '@/components/ui/label';
  7. import { Alert, AlertDescription } from '@/components/ui/alert';
  8. import { Plus, Edit, Trash2, Users, ArrowLeft } from 'lucide-react';
  9. import Link from 'next/link';
  10. interface Student {
  11. id: string;
  12. firstName: string;
  13. lastName: string;
  14. email: string;
  15. cedula: string;
  16. phone: string;
  17. isActive: boolean;
  18. createdAt: string;
  19. }
  20. interface FormData {
  21. firstName: string;
  22. lastName: string;
  23. email: string;
  24. cedula: string;
  25. phone: string;
  26. }
  27. export default function StudentsPage() {
  28. const [students, setStudents] = useState<Student[]>([]);
  29. const [isLoading, setIsLoading] = useState(true);
  30. const [showForm, setShowForm] = useState(false);
  31. const [editingStudent, setEditingStudent] = useState<Student | null>(null);
  32. const [formData, setFormData] = useState<FormData>({
  33. firstName: '',
  34. lastName: '',
  35. email: '',
  36. cedula: '',
  37. phone: '',
  38. });
  39. const [error, setError] = useState('');
  40. const [success, setSuccess] = useState('');
  41. useEffect(() => {
  42. fetchStudents();
  43. }, []);
  44. const fetchStudents = async () => {
  45. try {
  46. const response = await fetch('/api/admin/students');
  47. if (response.ok) {
  48. const data = await response.json();
  49. setStudents(data);
  50. }
  51. } catch (error) {
  52. console.error('Error fetching students:', error);
  53. } finally {
  54. setIsLoading(false);
  55. }
  56. };
  57. const handleSubmit = async (e: React.FormEvent) => {
  58. e.preventDefault();
  59. setError('');
  60. setSuccess('');
  61. try {
  62. const url = editingStudent
  63. ? `/api/admin/students/${editingStudent.id}`
  64. : '/api/admin/students';
  65. const method = editingStudent ? 'PUT' : 'POST';
  66. const response = await fetch(url, {
  67. method,
  68. headers: {
  69. 'Content-Type': 'application/json',
  70. },
  71. body: JSON.stringify(formData),
  72. });
  73. if (response.ok) {
  74. setSuccess(editingStudent ? 'Estudiante actualizado exitosamente' : 'Estudiante creado exitosamente');
  75. setShowForm(false);
  76. setEditingStudent(null);
  77. setFormData({ firstName: '', lastName: '', email: '', cedula: '', phone: '' });
  78. fetchStudents();
  79. } else {
  80. const errorData = await response.json();
  81. setError(errorData.error || 'Error al procesar la solicitud');
  82. }
  83. } catch (error) {
  84. setError('Error de conexión');
  85. }
  86. };
  87. const handleEdit = (student: Student) => {
  88. setEditingStudent(student);
  89. setFormData({
  90. firstName: student.firstName,
  91. lastName: student.lastName,
  92. email: student.email,
  93. cedula: student.cedula,
  94. phone: student.phone,
  95. });
  96. setShowForm(true);
  97. setError('');
  98. setSuccess('');
  99. };
  100. const handleDelete = async (studentId: string) => {
  101. if (!confirm('¿Estás seguro de que deseas eliminar este estudiante?')) {
  102. return;
  103. }
  104. try {
  105. const response = await fetch(`/api/admin/students/${studentId}`, {
  106. method: 'DELETE',
  107. });
  108. if (response.ok) {
  109. setSuccess('Estudiante eliminado exitosamente');
  110. fetchStudents();
  111. } else {
  112. const errorData = await response.json();
  113. setError(errorData.error || 'Error al eliminar el estudiante');
  114. }
  115. } catch (error) {
  116. setError('Error de conexión');
  117. }
  118. };
  119. const resetForm = () => {
  120. setShowForm(false);
  121. setEditingStudent(null);
  122. setFormData({ firstName: '', lastName: '', email: '', cedula: '', phone: '' });
  123. setError('');
  124. setSuccess('');
  125. };
  126. if (isLoading) {
  127. return (
  128. <div className="container mx-auto p-6">
  129. <div className="flex items-center justify-center h-64">
  130. <div className="text-lg">Cargando estudiantes...</div>
  131. </div>
  132. </div>
  133. );
  134. }
  135. return (
  136. <div className="container mx-auto p-6">
  137. <div className="flex items-center justify-between mb-6">
  138. <div className="flex items-center gap-4">
  139. <Link href="/admin/dashboard">
  140. <Button variant="outline" size="sm">
  141. <ArrowLeft className="h-4 w-4 mr-2" />
  142. Volver al Dashboard
  143. </Button>
  144. </Link>
  145. <div className="flex items-center gap-2">
  146. <Users className="h-6 w-6" />
  147. <h1 className="text-2xl font-bold">Gestión de Estudiantes</h1>
  148. </div>
  149. </div>
  150. <Button onClick={() => setShowForm(true)} className="flex items-center gap-2">
  151. <Plus className="h-4 w-4" />
  152. Nuevo Estudiante
  153. </Button>
  154. </div>
  155. {error && (
  156. <Alert className="mb-4 border-red-200 bg-red-50">
  157. <AlertDescription className="text-red-800">{error}</AlertDescription>
  158. </Alert>
  159. )}
  160. {success && (
  161. <Alert className="mb-4 border-green-200 bg-green-50">
  162. <AlertDescription className="text-green-800">{success}</AlertDescription>
  163. </Alert>
  164. )}
  165. {showForm && (
  166. <Card className="mb-6">
  167. <CardHeader>
  168. <CardTitle>
  169. {editingStudent ? 'Editar Estudiante' : 'Nuevo Estudiante'}
  170. </CardTitle>
  171. </CardHeader>
  172. <CardContent>
  173. <form onSubmit={handleSubmit} className="space-y-4">
  174. <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
  175. <div>
  176. <Label htmlFor="firstName">Nombres</Label>
  177. <Input
  178. id="firstName"
  179. value={formData.firstName}
  180. onChange={(e) => setFormData({ ...formData, firstName: e.target.value })}
  181. required
  182. />
  183. </div>
  184. <div>
  185. <Label htmlFor="lastName">Apellidos</Label>
  186. <Input
  187. id="lastName"
  188. value={formData.lastName}
  189. onChange={(e) => setFormData({ ...formData, lastName: e.target.value })}
  190. required
  191. />
  192. </div>
  193. <div>
  194. <Label htmlFor="email">Correo Electrónico</Label>
  195. <Input
  196. id="email"
  197. type="email"
  198. value={formData.email}
  199. onChange={(e) => setFormData({ ...formData, email: e.target.value })}
  200. required
  201. />
  202. </div>
  203. <div>
  204. <Label htmlFor="cedula">Cédula</Label>
  205. <Input
  206. id="cedula"
  207. value={formData.cedula}
  208. onChange={(e) => setFormData({ ...formData, cedula: e.target.value })}
  209. required
  210. />
  211. </div>
  212. <div>
  213. <Label htmlFor="phone">Teléfono</Label>
  214. <Input
  215. id="phone"
  216. value={formData.phone}
  217. onChange={(e) => setFormData({ ...formData, phone: e.target.value })}
  218. required
  219. />
  220. </div>
  221. </div>
  222. <div className="flex gap-2">
  223. <Button type="submit">
  224. {editingStudent ? 'Actualizar' : 'Crear'} Estudiante
  225. </Button>
  226. <Button type="button" variant="outline" onClick={resetForm}>
  227. Cancelar
  228. </Button>
  229. </div>
  230. </form>
  231. </CardContent>
  232. </Card>
  233. )}
  234. <Card>
  235. <CardHeader>
  236. <CardTitle>Lista de Estudiantes ({students.length})</CardTitle>
  237. </CardHeader>
  238. <CardContent>
  239. {students.length === 0 ? (
  240. <div className="text-center py-8 text-gray-500">
  241. No hay estudiantes registrados
  242. </div>
  243. ) : (
  244. <div className="overflow-x-auto">
  245. <table className="w-full border-collapse">
  246. <thead>
  247. <tr className="border-b">
  248. <th className="text-left p-2">Nombre</th>
  249. <th className="text-left p-2">Email</th>
  250. <th className="text-left p-2">Cédula</th>
  251. <th className="text-left p-2">Teléfono</th>
  252. <th className="text-left p-2">Estado</th>
  253. <th className="text-left p-2">Acciones</th>
  254. </tr>
  255. </thead>
  256. <tbody>
  257. {students.map((student) => (
  258. <tr key={student.id} className="border-b hover:bg-gray-50">
  259. <td className="p-2">
  260. {student.firstName} {student.lastName}
  261. </td>
  262. <td className="p-2">{student.email}</td>
  263. <td className="p-2">{student.cedula}</td>
  264. <td className="p-2">{student.phone}</td>
  265. <td className="p-2">
  266. <span className={`px-2 py-1 rounded-full text-xs ${
  267. student.isActive
  268. ? 'bg-green-100 text-green-800'
  269. : 'bg-red-100 text-red-800'
  270. }`}>
  271. {student.isActive ? 'Activo' : 'Inactivo'}
  272. </span>
  273. </td>
  274. <td className="p-2">
  275. <div className="flex gap-2">
  276. <Button
  277. size="sm"
  278. variant="outline"
  279. onClick={() => handleEdit(student)}
  280. >
  281. <Edit className="h-4 w-4" />
  282. </Button>
  283. <Button
  284. size="sm"
  285. variant="outline"
  286. onClick={() => handleDelete(student.id)}
  287. className="text-red-600 hover:text-red-700"
  288. >
  289. <Trash2 className="h-4 w-4" />
  290. </Button>
  291. </div>
  292. </td>
  293. </tr>
  294. ))}
  295. </tbody>
  296. </table>
  297. </div>
  298. )}
  299. </CardContent>
  300. </Card>
  301. </div>
  302. );
  303. }