From 544d089824805f39a1973b078be99d125d882b9d Mon Sep 17 00:00:00 2001 From: miguel Date: Mon, 23 Jun 2025 18:59:59 -0600 Subject: [PATCH] feat: implement event and questionnaire creation with updated forms; add editing capabilities for questionnaires and events --- .../administrador/crear-evento/page.tsx | 45 +++--- .../[id_evento]/[id_cuestionario]/page.tsx | 52 +++++- .../administrador/evento/[id_evento]/page.tsx | 11 +- src/components/evento-card.tsx | 2 +- src/containers/create-evento.tsx | 24 +-- src/containers/create-formulario.tsx | 28 +++- src/containers/edit-evento.tsx | 16 +- src/containers/edit-formulario.tsx | 151 ++++++++++++++++++ src/data/plantillas.ts | 1 - src/types/create-evento.d.ts | 1 - src/types/create-formulario.d.ts | 1 + src/types/cuestionario.d.ts | 2 + 12 files changed, 265 insertions(+), 69 deletions(-) create mode 100644 src/containers/edit-formulario.tsx diff --git a/src/app/(admins)/administrador/crear-evento/page.tsx b/src/app/(admins)/administrador/crear-evento/page.tsx index 58e8647..2affa1b 100644 --- a/src/app/(admins)/administrador/crear-evento/page.tsx +++ b/src/app/(admins)/administrador/crear-evento/page.tsx @@ -53,36 +53,31 @@ export default function Page() { const handleCrearEventoYFormulario = async () => { try { - const createEvento = await axiosInstance.post('/evento', evento); + const createEventoWithCuestionario = await axiosInstance.post('/cuestionario/withEvento', { + evento: evento, + cuestionario: datosFormulario, + }); - if (eventoBanner) { + if (createEventoWithCuestionario) { const formData = new FormData(); - formData.append('banner', eventoBanner); - const bannercreated = await axiosInstance.post( - `/evento/${createEvento.data.id_evento}/banner`, - formData, - { - headers: { - 'Content-Type': 'multipart/form-data', - }, - } - ); - - console.log('Banner creado:', bannercreated.data); + if (eventoBanner) { + formData.append('banner', eventoBanner); + const bannercreated = await axiosInstance.post( + `/evento/${createEventoWithCuestionario.data.id_evento}/banner`, + formData, + { + headers: { + 'Content-Type': 'multipart/form-data', + }, + } + ); + console.log('Banner creado:', bannercreated.data); + } } - console.log('Evento creado:', createEvento.data); - console.log('Datos del formulario:', datosFormulario); - - const createFormulario = await axiosInstance.post('/cuestionario', { - id_evento: createEvento.data.id_evento, - cupo_maximo: Number(evento.cupo_maximo), - ...datosFormulario, - }); - if (createFormulario.data.id_formulario) { - console.log('Formulario creado:', createFormulario.data); - } + console.log('Evento y formulario creados:', createEventoWithCuestionario.data); + toast.success('Evento y formulario creados exitosamente'); router.push(`/administrador`); } catch (error) { const msg = getAxiosError(error); diff --git a/src/app/(admins)/administrador/evento/[id_evento]/[id_cuestionario]/page.tsx b/src/app/(admins)/administrador/evento/[id_evento]/[id_cuestionario]/page.tsx index c2152bc..c486950 100644 --- a/src/app/(admins)/administrador/evento/[id_evento]/[id_cuestionario]/page.tsx +++ b/src/app/(admins)/administrador/evento/[id_evento]/[id_cuestionario]/page.tsx @@ -1,13 +1,14 @@ 'use client'; import SimpleInput from '@/components/input'; import Table, { Header } from '@/components/table'; +import EditFormulario from '@/containers/edit-formulario'; import { useGetApi } from '@/hooks/use-get-api'; import { GetCuestionario } from '@/types/cuestionario'; import { ParticipacionEvento } from '@/types/participante-evento'; import axiosInstance from '@/utils/api-config'; import Link from 'next/link'; import { useParams } from 'next/navigation'; -import React from 'react'; +import React, { useEffect } from 'react'; import toast from 'react-hot-toast'; type Params = { @@ -17,6 +18,9 @@ type Params = { export default function Page() { const params = useParams(); + + const [cuestionario, setCuestionario] = + React.useState(null); const [busquedaCorreo, setBusquedaCorreo] = React.useState(''); const [loadingAsistencia, setLoadingAsistencia] = React.useState< Record @@ -25,10 +29,14 @@ export default function Page() { const { data: participantes, setData } = useGetApi( `/participante-evento/evento/${params.id_cuestionario}` ); - const { data: cuestionario } = useGetApi( + const { data } = useGetApi( `/cuestionario/${params.id_cuestionario}` ); + useEffect(() => { + if (data) setCuestionario(data); + }, [data]); + const confirmarAsistencia = async ( id_participante: number, id_evento: number @@ -109,6 +117,23 @@ export default function Page() { }, ]; + const handleChange = (field: keyof GetCuestionario, value: string | Date) => { + setCuestionario((prev) => + prev + ? { + ...prev, + [field]: value, + } + : null + ); + }; + + const handleCuestionarioActualizado = ( + eventoActualizado: GetCuestionario + ) => { + setCuestionario(eventoActualizado); + }; + const participantesFiltrados = participantes && participantes.filter((p) => @@ -116,11 +141,7 @@ export default function Page() { ); return ( -
-

{cuestionario?.nombre_form}

-

- Aquí puedes ver los participantes registrados y confirmar su asistencia. -

+
Volver + +

Formulario

+ + {cuestionario && ( + + )} + +

Participantes

+ + {participantes?.length === 0 && ( +

No hay participantes registrados aún.

+ )} + {participantes && participantes.length > 0 && ( <> +
+ + + Volver + +

Evento

{evento.cuestionarios[0]?.cupo_maximo === null ? ( - Sin límite + Sin límite ) : ( {evento.cuestionarios[0]?.cupos_disponibles} cupos disponibles diff --git a/src/containers/create-evento.tsx b/src/containers/create-evento.tsx index ea7cfba..4885b75 100644 --- a/src/containers/create-evento.tsx +++ b/src/containers/create-evento.tsx @@ -33,24 +33,12 @@ export default function CreateEvento({ -
- handleChange('nombre_evento', e.target.value)} - /> -
- -
- handleChange('cupo_maximo', e.target.value)} - /> -
+ handleChange('nombre_evento', e.target.value)} + />
diff --git a/src/containers/create-formulario.tsx b/src/containers/create-formulario.tsx index d7ba936..458a1fa 100644 --- a/src/containers/create-formulario.tsx +++ b/src/containers/create-formulario.tsx @@ -33,7 +33,7 @@ const tiposValidacion: TiposValidacion[] = [ 'cuenta_alumno', 'comunidad_trabajador', 'cuenta_trabajador', - 'rfc' + 'rfc', ]; type CampoPreguntaSimple = @@ -140,14 +140,28 @@ export default function FormularioEditor({ formulario, onChange }: Props) { }; return ( -
+

Editar Formulario

- actualizarCampo('nombre_form', e.target.value)} - /> +
+ actualizarCampo('nombre_form', e.target.value)} + /> +
+ +
+ + actualizarCampo('cupo_maximo', Number(e.target.value)) + } + /> +
isoString.slice(0, 10); const [loading, setLoading] = useState(false); const [nuevoBanner, setNuevoBanner] = useState(null); @@ -132,20 +132,22 @@ export default function EditEvento({
handleChange('fecha_inicio', e.target.value)} + value={formatDateLocal(new Date(evento.fecha_inicio))} + onChange={(e) => + handleChange('fecha_inicio', new Date(e.target.value)) + } />
handleChange('fecha_fin', e.target.value)} + value={formatDateLocal(new Date(evento.fecha_fin))} + onChange={(e) => handleChange('fecha_fin', new Date(e.target.value))} />
diff --git a/src/containers/edit-formulario.tsx b/src/containers/edit-formulario.tsx new file mode 100644 index 0000000..ec77018 --- /dev/null +++ b/src/containers/edit-formulario.tsx @@ -0,0 +1,151 @@ +import ImageUploader from '@/components/banner-uploader'; +import Button from '@/components/button'; +import SimpleInput from '@/components/input'; +import { GetCuestionario } from '@/types/cuestionario'; +import axiosInstance from '@/utils/api-config'; +import { formatDateLocal } from '@/utils/date-utils'; +import { getAxiosError } from '@/utils/errors-utils'; +import MDEditor, { commands } from '@uiw/react-md-editor'; +import React, { useState } from 'react'; +import toast from 'react-hot-toast'; + +interface EditFormularioProps { + cuestionario: GetCuestionario; + handleChange: (field: keyof GetCuestionario, value: string | Date) => void; + handleOnChange: (eventoActualizado: GetCuestionario) => void; +} + +export default function EditFormulario({ + cuestionario, + handleChange, +}: EditFormularioProps) { + const [loading, setLoading] = useState(false); + const [nuevoBanner, setNuevoBanner] = useState(null); + + const handleOnSave = async () => { + setLoading(true); + try { + // 1. Subir nuevo banner si fue cambiado + if (nuevoBanner) { + const formData = new FormData(); + formData.append('banner', nuevoBanner); + + await axiosInstance.post( + `/cuestionario/${cuestionario.id_cuestionario}/banner`, + formData, + { + headers: { + 'Content-Type': 'multipart/form-data', + }, + } + ); + } + + // 2. Actualizar el evento (sin incluir 'banner') + const res = await axiosInstance.patch( + `/cuestionario/${cuestionario.id_cuestionario}`, + { + nombre_form: cuestionario.nombre_form, + descripcion: cuestionario.descripcion, + fecha_inicio: cuestionario.fecha_inicio, + fecha_fin: cuestionario.fecha_fin, + } + ); + + console.log('Evento actualizado:', res.data); + + toast.success('Evento actualizado correctamente'); + } catch (error) { + const msg = getAxiosError(error); + toast.error(`Error al actualizar el evento: ${msg.message}`); + } finally { + setLoading(false); + } + }; + + return ( +
+

Editar Información del formulario

+

Modifica los datos del evento que desees actualizar.

+ + { + setNuevoBanner(file); + }} + /> + +
+ handleChange('nombre_form', e.target.value)} + /> +
+
+ + handleChange('cupo_maximo', e.target.value ? e.target.value : '') + } + /> +
+ +
+ +
+ { + const texto = value || ''; + if (texto.length <= 500) { + handleChange( + 'descripcion_evento' as keyof GetCuestionario, + texto + ); + } + }} + height={250} + commands={[commands.bold, commands.italic, commands.hr]} + /> +
+
+ +
+ + handleChange('fecha_inicio', new Date(e.target.value)) + } + /> +
+ +
+ handleChange('fecha_fin', new Date(e.target.value))} + /> +
+ +
+ +
+
+ ); +} diff --git a/src/data/plantillas.ts b/src/data/plantillas.ts index 9b3b98e..2e547cc 100644 --- a/src/data/plantillas.ts +++ b/src/data/plantillas.ts @@ -165,5 +165,4 @@ Inicia esta nueva etapa con un recorrido especial por nuestras instalaciones, pe /* Recorrido de bienvenida para nuevo personal Damos la más cordial bienvenida al personal de nuevo ingreso a nuestra Facultad. Este recorrido está diseñado para familiarizarlos con las principales áreas administrativas, académicas y de servicios. También tendrán la oportunidad de conocer al equipo de trabajo y obtener información clave para integrarse con éxito a la comunidad universitaria. - */ diff --git a/src/types/create-evento.d.ts b/src/types/create-evento.d.ts index b553fda..edee5f4 100644 --- a/src/types/create-evento.d.ts +++ b/src/types/create-evento.d.ts @@ -4,5 +4,4 @@ export interface CreateEventoType { descripcion_evento?: string; // Es opciona fecha_inicio: Date; fecha_fin: Date; - cupo_maximo?: number; // Es opcional } diff --git a/src/types/create-formulario.d.ts b/src/types/create-formulario.d.ts index 956df65..1add13b 100644 --- a/src/types/create-formulario.d.ts +++ b/src/types/create-formulario.d.ts @@ -27,6 +27,7 @@ export interface FormularioCreacion { fecha_inicio: string; // ISO 8601 date string fecha_fin: string; id_tipo_cuestionario: number; + cupo_maximo?: number; // Opcional, si no se requiere un cupo máximo secciones: SeccionFormulario[]; } diff --git a/src/types/cuestionario.d.ts b/src/types/cuestionario.d.ts index f4ad907..15485dc 100644 --- a/src/types/cuestionario.d.ts +++ b/src/types/cuestionario.d.ts @@ -4,8 +4,10 @@ export interface GetCuestionario { id_cuestionario: number; nombre_form: string; + banner?: string; descripcion: string; contador_secciones: number; + cupo_maximo?: number | null; // Puede ser null si no hay límite editable: boolean; fecha_fin: string; // Formato ISO: 'YYYY-MM-DDTHH:mm:ss' fecha_inicio: string; // Formato ISO: 'YYYY-MM-DDTHH:mm:ss'