diff --git a/public/banner-guapa.webp b/public/banner-guapa.webp new file mode 100644 index 0000000..14055f1 Binary files /dev/null and b/public/banner-guapa.webp differ diff --git a/src/app/(admins)/user/evento/[id_evento]/formularios/crear/page.tsx b/src/app/(admins)/user/evento/[id_evento]/formularios/crear/page.tsx index ecc009e..f8fafba 100644 --- a/src/app/(admins)/user/evento/[id_evento]/formularios/crear/page.tsx +++ b/src/app/(admins)/user/evento/[id_evento]/formularios/crear/page.tsx @@ -12,6 +12,8 @@ import { useParams, useRouter } from 'next/navigation'; import axiosInstance from '@/utils/api-config'; import { getAxiosError } from '@/utils/errors-utils'; import toast from 'react-hot-toast'; +import { useGetApi } from '@/hooks/use-get-api'; +import { TipoEvento } from '@/types/evento'; type Params = { id_evento: string; @@ -23,6 +25,7 @@ export default function Page() { const { evento, refetch } = useEvento(); const [datosFormulario, setDatosFormulario] = useState(null); + const { data: tipo_eventos } = useGetApi(`/tipo-evento`); const handleSeleccionarPlantilla = (plantillaId: string) => { const seleccionada = plantillasDisponibles.find( @@ -223,6 +226,10 @@ export default function Page() { formulario: datosFormulario, onChange: (nuevo) => setDatosFormulario({ ...nuevo }), evento: evento ? evento : undefined, + tipo_eventos: tipo_eventos?.map((tipo) => ({ + value: tipo.id_tipo_evento, + label: tipo.tipo_evento, + })), }); return formularioEditor.informacionBasica(); })()} diff --git a/src/app/(admins)/user/usuarios/page.tsx b/src/app/(admins)/user/usuarios/page.tsx new file mode 100644 index 0000000..540c060 --- /dev/null +++ b/src/app/(admins)/user/usuarios/page.tsx @@ -0,0 +1,45 @@ +'use client'; +import Button from '@/components/button'; +import Table, { Header } from '@/components/table'; +import { useGetApi } from '@/hooks/use-get-api'; +import { Administrador } from '@/types/administradores'; +import React from 'react'; + +export default function Page() { + const { data } = useGetApi('/administrador'); + + const headers: Header[] = [ + { key: 'id_administrador', label: 'ID' }, + { key: 'nombre_usuario', label: 'Nombre de Usuario' }, + { key: 'correo', label: 'Correo' }, + { + key: 'tipoUser', + render: (_, row) => row.tipoUser.tipo, + label: 'Tipo de Usuario', + }, + { + key: 'tipoUser', + render: () => ( +
+ + +
+ ), + label: 'Acciones', + }, + ]; + + return ( +
+
+

Administradores

+ +
+ {data && } + + ); +} diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx index cba6ccc..aa355b9 100644 --- a/src/app/(auth)/login/page.tsx +++ b/src/app/(auth)/login/page.tsx @@ -3,28 +3,13 @@ import SimpleInput from '@/components/input'; import PasswordInput from '@/components/password'; import React from 'react'; import { useRouter } from 'next/navigation'; +import axiosInstance from '@/utils/api-config'; export default function LoginForm() { const router = useRouter(); - // Fake user data - const fakeUsers = [ - { - username: 'user-admin', - password: '@dm1nP@ss', - role: 'administrator', - redirectPath: '/user/eventos', - }, - { - username: 'staff1', - password: 'st@ffP@ss', - role: 'staff', - redirectPath: '/user/eventos', - }, - ]; - const [loginData, setLoginData] = React.useState({ - username: '', + nombre_usuario: '', password: '', }); @@ -38,36 +23,53 @@ export default function LoginForm() { if (error) setError(''); }; - const handleSubmit = (e: React.FormEvent) => { + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setIsLoading(true); setError(''); - // Simulate API call delay - setTimeout(() => { - const user = fakeUsers.find( - (u) => - u.username === loginData.username && u.password === loginData.password - ); + try { + const response = await axiosInstance.post('/administrador/login', { + nombre_usuario: loginData.nombre_usuario, + password: loginData.password, + }); - if (user) { - // Store user data in localStorage (in a real app, you'd use proper session management) - localStorage.setItem( - 'user', - JSON.stringify({ - username: user.username, - role: user.role, - }) - ); + // Guardar el token en sessionStorage + if (response.data.token) { + sessionStorage.setItem('token', response.data.token); - // Redirect to appropriate dashboard - router.push(user.redirectPath); + // Opcional: guardar información adicional del usuario si viene en la respuesta + if (response.data.user) { + sessionStorage.setItem('user', JSON.stringify(response.data.user)); + } + + // Redireccionar al dashboard + router.push('/user/eventos'); } else { - setError('Usuario o contraseña incorrectos'); + setError('No se recibió token de autenticación'); } + } catch (error: unknown) { + console.error('Error en login:', error); + // Type guard para axios error + if (error && typeof error === 'object' && 'response' in error) { + const axiosError = error as { + response?: { status?: number; data?: { message?: string } }; + }; + + if (axiosError.response?.status === 401) { + setError('Credenciales incorrectas'); + } else if (axiosError.response?.data?.message) { + setError(axiosError.response.data.message); + } else { + setError('Error al iniciar sesión. Inténtalo de nuevo.'); + } + } else { + setError('Error al iniciar sesión. Inténtalo de nuevo.'); + } + } finally { setIsLoading(false); - }, 1000); + } }; return ( @@ -88,7 +90,7 @@ export default function LoginForm() { Sistema de Eventos

- Ingresa tu nombre de usuario y tu contraseña para acceder + Ingresa tu correo electrónico y tu contraseña para acceder a tu cuenta.

@@ -101,9 +103,9 @@ export default function LoginForm() { diff --git a/src/components/evento/evento-card-admin.tsx b/src/components/evento/evento-card-admin.tsx index 2367ff3..a6587a2 100644 --- a/src/components/evento/evento-card-admin.tsx +++ b/src/components/evento/evento-card-admin.tsx @@ -105,7 +105,7 @@ export default function EventoCardAdmin({ Formularios disponibles:
- {evento.cuestionarios.map((cuestionario) => ( + {evento.cuestionarios.slice(0, 3).map((cuestionario) => (
diff --git a/src/components/formulario/formulario-card-create-preview.tsx b/src/components/formulario/formulario-card-create-preview.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/formulario/formulario-card-preview-unified.tsx b/src/components/formulario/formulario-card-preview-unified.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/formulario/formulario-card-user.tsx b/src/components/formulario/formulario-card-user.tsx index b9c8008..c3fd5ab 100644 --- a/src/components/formulario/formulario-card-user.tsx +++ b/src/components/formulario/formulario-card-user.tsx @@ -5,6 +5,7 @@ import Link from 'next/link'; import React, { useState } from 'react'; import MarkdownRenderer from '../markdown-render'; import { toParam } from '@/utils/slugify'; +import { formatearFechaCard, formatearHorario } from '@/utils/date-utils'; import Button from '../button'; export default function FormularioCardUser({ @@ -22,6 +23,22 @@ export default function FormularioCardUser({ const sinCupos = formulario.cupo_maximo !== null && formulario.cupos_disponibles <= 0; + // Verificar si el evento es el mismo día + const fechaInicio = new Date(formulario.fecha_inicio); + const fechaFin = new Date(formulario.fecha_fin); + const esElMismoDia = fechaInicio.toDateString() === fechaFin.toDateString(); + + // Obtener información de fecha formateada usando las utilidades + const { mesTexto, diaTexto } = formatearFechaCard( + formulario.fecha_inicio, + formulario.fecha_fin + ); + + // Obtener horario formateado si es el mismo día + const horarioFormateado = esElMismoDia + ? formatearHorario(formulario.fecha_inicio, formulario.fecha_fin) + : null; + const descripcionRecortada = descripcion.length > limite && !verMas ? `${descripcion.slice(0, limite)}...` @@ -78,7 +95,7 @@ export default function FormularioCardUser({ {/* Badge de cupos en la esquina superior derecha */}
- {formulario.tipoCuestionario.tipo_cuestionario} + {formulario.tipoCuestionario.tipo_cuestionario}{' '}
@@ -87,34 +104,24 @@ export default function FormularioCardUser({ {/* Fecha del evento */}
-
- {new Date(formulario.fecha_inicio) - .toLocaleDateString('es-ES', { month: 'short' }) - .toUpperCase()} -
+
{mesTexto}
- {(() => { - const fechaInicio = new Date(formulario.fecha_inicio); - const fechaFin = new Date(formulario.fecha_fin); - const diaInicio = fechaInicio.getDate(); - const diaFin = fechaFin.getDate(); - const esElMismoDia = - fechaInicio.toDateString() === fechaFin.toDateString(); - - return esElMismoDia ? diaInicio : `${diaInicio} - ${diaFin}`; - })()} + {diaTexto}
- - {evento.nombre_evento} - + {evento.nombre_evento}
{formulario.nombre_form}
+ {formulario.tipoEvento && ( +
+ {formulario.tipoEvento.tipo_evento} +
+ )}
@@ -133,27 +140,50 @@ export default function FormularioCardUser({
{/* Información de cupos y estadísticas */} -
-
-
- -
- Cupos - - {formulario.cupo_maximo !== null - ? `${formulario.cupos_usados} / ${formulario.cupo_maximo}` - : 'Sin límite'} - - {sinCupos && ( -
- - Cupos agotados +
+ {/* Columna de horario - solo visible si es el mismo día */} + {esElMismoDia && ( +
+
+
+ +
+ Horario + + {horarioFormateado} +
- )} +
+
+
+ )} + {/* Columna de cupos - siempre visible */} +
+
+
+ +
+ Cupos + + {formulario.cupo_maximo !== null + ? `${formulario.cupos_usados} / ${formulario.cupo_maximo}` + : 'Sin límite'} + + {sinCupos && ( +
+ + Cupos agotados +
+ )} +
diff --git a/src/components/navbar.tsx b/src/components/navbar.tsx index b62154a..09577cb 100644 --- a/src/components/navbar.tsx +++ b/src/components/navbar.tsx @@ -11,6 +11,7 @@ const Navbar: React.FC = () => { { href: '/user/eventos', label: 'Eventos' }, { href: '/user/qr', label: 'Lector de QRs' }, { href: '/user/eventos/crear', label: 'Crear Evento' }, + { href: '/user/usuarios', label: 'Usuarios' }, { href: '/signout', label: 'Cerrar sesión' }, ]; diff --git a/src/components/select.tsx b/src/components/select.tsx index 364d43c..b8a2666 100644 --- a/src/components/select.tsx +++ b/src/components/select.tsx @@ -1,6 +1,6 @@ import React, { useId } from 'react'; -interface Option { +export interface Option { value: string | number; label: string; } @@ -74,7 +74,10 @@ const Select: React.FC = ({ return (
-
); diff --git a/src/containers/formulario/formulario-registro.tsx b/src/containers/formulario/formulario-registro.tsx index 992b3ac..d8b9e70 100644 --- a/src/containers/formulario/formulario-registro.tsx +++ b/src/containers/formulario/formulario-registro.tsx @@ -9,6 +9,7 @@ import Button from '@/components/button'; import toast from 'react-hot-toast'; import { useRouter } from 'next/navigation'; import { getAxiosError } from '@/utils/errors-utils'; +import { SubmitResponse } from '@/types/submit'; export type UsuarioDataResponse = { cuenta: string | null; @@ -35,8 +36,13 @@ type RespuestaFormulario = Record; // id_pregunta: resp export default function FormularioRegistro({ id_cuestionario, + handleSubmitFormulario, }: { + evento: string; + cuestionario: string; + id_evento: number; id_cuestionario: number; + handleSubmitFormulario: (data: SubmitResponse) => void; }) { // ------------------------ // Estados @@ -77,7 +83,8 @@ export default function FormularioRegistro({ // Efecto: buscar información de cuenta // ------------------------ useEffect(() => { - const cuenta = respuestas[preguntaCuenta?.id_pregunta ?? '']; + const cuentaId = preguntaCuenta?.id_pregunta ?? ''; + const cuenta = respuestas[cuentaId]; const esCuentaAlumno = preguntaCuenta?.validacion === 'cuenta_alumno'; const esCuentaTrabajador = @@ -130,7 +137,7 @@ export default function FormularioRegistro({ } else { setCuentaInfo(null); } - }, [respuestas[preguntaCuenta?.id_pregunta ?? '']]); + }, [respuestas, preguntaCuenta?.id_pregunta, preguntaCuenta?.validacion]); // ------------------------ // Efecto: precargar respuestas si hay cuentaInfo @@ -212,7 +219,6 @@ export default function FormularioRegistro({ }; const handleOnSubmit = async () => { - console.log('Enviando respuestas:', respuestas); setIsSending(true); if (!data?.cuestionario.id_cuestionario) { alert('No se ha cargado correctamente el formulario'); @@ -273,7 +279,7 @@ export default function FormularioRegistro({ }; try { - const res = await axiosInstance.post( + const res = await axiosInstance.post( '/cuestionario-respondido/submit', payload ); @@ -282,7 +288,7 @@ export default function FormularioRegistro({ - {res.data.message} + Respuestas Enviadas Con Éxito ) : ( <> @@ -308,13 +314,13 @@ export default function FormularioRegistro({ }, } ); + + handleSubmitFormulario(res.data); } catch (error) { console.log(getAxiosError(error)); toast.error('Hubo un error al enviar el formulario', { duration: 5000, }); - } finally { - // Redirigir al usuario después de enviar el formulario router.push('/'); } }; @@ -367,7 +373,6 @@ export default function FormularioRegistro({ />
)} - {isComunidad && confirmativeOption.some((opt) => isComunidad.label?.toLowerCase().includes(opt.toLowerCase()) @@ -404,7 +409,6 @@ export default function FormularioRegistro({ />
)} - {mostrarFormularioRestante && data?.cuestionario.secciones.map((seccion, i) => (
@@ -535,7 +539,6 @@ export default function FormularioRegistro({ })}
))} -