diff --git a/src/app/(admins)/administrador/crear-evento/page.tsx b/src/app/(admins)/administrador/crear-evento/page.tsx index 3b72c63..58e8647 100644 --- a/src/app/(admins)/administrador/crear-evento/page.tsx +++ b/src/app/(admins)/administrador/crear-evento/page.tsx @@ -76,6 +76,7 @@ export default function Page() { const createFormulario = await axiosInstance.post('/cuestionario', { id_evento: createEvento.data.id_evento, + cupo_maximo: Number(evento.cupo_maximo), ...datosFormulario, }); if (createFormulario.data.id_formulario) { 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 0bca7d1..c2152bc 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,17 +1,14 @@ 'use client'; import SimpleInput from '@/components/input'; -import EditEvento from '@/containers/edit-evento'; +import Table, { Header } from '@/components/table'; import { useGetApi } from '@/hooks/use-get-api'; -import { GetEventoWithCuestionario } from '@/types/evento'; -import { useParams } from 'next/navigation'; -import React, { useState, useEffect } from 'react'; -import { CreateEventoType } from '@/types/create-evento'; +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 toast from 'react-hot-toast'; -import Table, { Header } from '@/components/table'; -import Button from '@/components/button'; -import { downloadFile } from '@/utils/downloas-utils'; type Params = { id_evento: string; @@ -20,37 +17,17 @@ type Params = { export default function Page() { const params = useParams(); - const { data } = useGetApi( - `/evento/${params.id_evento}/cuestionario/${params.id_cuestionario}` - ); - const { data: participantes, setData } = useGetApi( - `/participante-evento/evento/${params.id_cuestionario}` - ); - - const [evento, setEvento] = useState(null); const [busquedaCorreo, setBusquedaCorreo] = React.useState(''); - const [loading, setLoading] = useState(false); const [loadingAsistencia, setLoadingAsistencia] = React.useState< Record >({}); - useEffect(() => { - if (data) setEvento(data); - }, [data]); - - const handleChange = ( - field: keyof CreateEventoType, - value: string | Date - ) => { - setEvento((prev) => - prev - ? { - ...prev, - [field]: value, - } - : null - ); - }; + const { data: participantes, setData } = useGetApi( + `/participante-evento/evento/${params.id_cuestionario}` + ); + const { data: cuestionario } = useGetApi( + `/cuestionario/${params.id_cuestionario}` + ); const confirmarAsistencia = async ( id_participante: number, @@ -76,12 +53,6 @@ export default function Page() { } }; - const handleEventoActualizado = ( - eventoActualizado: GetEventoWithCuestionario - ) => { - setEvento(eventoActualizado); - }; - const headers: Header[] = [ { key: 'id_participante', @@ -144,63 +115,19 @@ export default function Page() { p.participante.correo.toLowerCase().includes(busquedaCorreo.toLowerCase()) ); - if (!evento) return

Cargando...

; - - const handleDownload = async () => { - setLoading(true); - - - try { - const res = await axiosInstance.get( - `/cuestionario-respondido/reporte-respuestas/${params.id_cuestionario}`, - { - responseType: 'blob', - } - ); - - downloadFile( - res.data, - `reporte_respuestas_formulario_${params.id_cuestionario}`, - 'csv' - ); - } catch (error) { - console.error('Error al descargar el archivo:', error); - toast.error('Error al descargar el archivo'); - } finally { - setLoading(false); - } - }; - return ( -
-

Evento

- - - -
-

Participantes

-
- -
-
- +
+

{cuestionario?.nombre_form}

+

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

+ + + Volver + {participantes && participantes.length > 0 && ( <> (); + const { data } = useGetApi( + `/evento/${params.id_evento}/cuestionarios` + ); + + const [evento, setEvento] = useState(null); + const [loading, setLoading] = useState(false); + + useEffect(() => { + if (data) setEvento(data); + }, [data]); + + const handleChange = ( + field: keyof CreateEventoType, + value: string | Date + ) => { + setEvento((prev) => + prev + ? { + ...prev, + [field]: value, + } + : null + ); + }; + + const handleEventoActualizado = ( + eventoActualizado: GetEventoWithCuestionarios + ) => { + setEvento(eventoActualizado); + }; + + const headers: Header[] = [ + { + key: 'nombre_form', + label: 'Nombre del cuestionario', + }, + { + key: 'cupo_maximo', + label: 'Cupos', + render: (_, row) => { + if (row.cupo_maximo === null) { + return 'Sin límite'; + } + + return `${row.cupos_usados ?? 0} / ${row.cupo_maximo}`; + }, + }, + { + key: 'banner', + label: 'Ver / Editar', + render: (_, row) => ( +
+ + Ver/Editar formulario + +
+ ), + }, + { + key: 'banner', + label: 'Descargar respuestas', + render: (_, row) => ( + + ), + }, + ]; + + if (!evento) return

Cargando...

; + + const handleDownload = async (id_cuestionario: number, nombre: string) => { + setLoading(true); + + try { + const res = await axiosInstance.get( + `/cuestionario-respondido/reporte-respuestas/${id_cuestionario}`, + { + responseType: 'blob', + } + ); + + downloadFile(res.data, `${nombre}`, 'csv'); + } catch (error) { + console.error('Error al descargar el archivo:', error); + toast.error('Error al descargar el archivo'); + } finally { + setLoading(false); + } + }; + + return ( +
+

Evento

+ + + + {data?.cuestionarios && data.cuestionarios.length > 0 && ( + <> +

Cuestionarios del evento

+
+ + + + )} + + ); +} diff --git a/src/components/evento-card.tsx b/src/components/evento-card.tsx index 08ad834..9b92e7e 100644 --- a/src/components/evento-card.tsx +++ b/src/components/evento-card.tsx @@ -1,3 +1,4 @@ +'use client'; import { GetEventoWithCuestionarios } from '@/types/evento'; import Image from 'next/image'; import Link from 'next/link'; @@ -12,7 +13,7 @@ export default function EventoCard({ user?: 'public' | 'administrador' | 'staff'; }) { const [verMas, setVerMas] = useState(false); - const descripcion = evento.descripcion_evento; + const descripcion = evento.descripcion_evento ?? ''; const limite = 100; const descripcionRecortada = @@ -26,11 +27,11 @@ export default function EventoCard({
+
+ {evento.cuestionarios[0]?.cupo_maximo === null ? ( + Sin límite + ) : ( + + {evento.cuestionarios[0]?.cupos_disponibles} cupos disponibles + + )} {descripcion.length > limite && (
+ {evento.cuestionarios.length > 0 && evento.cuestionarios.map((cuestionario) => (
- - Regístrate - + {user === 'administrador' || user === 'staff' ? ( + + Ver evento + + ) : cuestionario.cupos_disponibles === 0 && + cuestionario.cupo_maximo !== null ? ( + + ) : ( + + Regístrate + + )}
))}
diff --git a/src/containers/create-evento.tsx b/src/containers/create-evento.tsx index 239d987..ea7cfba 100644 --- a/src/containers/create-evento.tsx +++ b/src/containers/create-evento.tsx @@ -33,12 +33,24 @@ export default function CreateEvento({ - handleChange('nombre_evento', e.target.value)} - /> +
+ handleChange('nombre_evento', e.target.value)} + /> +
+ +
+ handleChange('cupo_maximo', e.target.value)} + /> +
@@ -66,6 +78,14 @@ export default function CreateEvento({ { label: 'Conferencia', value: 'conferencia' }, { label: 'Taller', value: 'taller' }, { label: 'Seminario', value: 'seminario' }, + { label: 'Curso', value: 'curso' }, + { label: 'Webinar', value: 'webinar' }, + { label: 'Reunión', value: 'reunion' }, + { label: 'Feria', value: 'feria' }, + { label: 'Concierto', value: 'concierto' }, + { label: 'Exposición', value: 'exposicion' }, + { label: 'Deportivo', value: 'deportivo' }, + { label: 'Cultural', value: 'cultural' }, { label: 'Otro', value: 'otro' }, ]} /> @@ -88,9 +108,7 @@ export default function CreateEvento({ type="datetime-local" className={{ container: 'mb-3' }} value={formatDateLocal(evento.fecha_fin)} - onChange={(e) => - handleChange('fecha_fin', new Date(e.target.value)) - } + onChange={(e) => handleChange('fecha_fin', new Date(e.target.value))} />
diff --git a/src/containers/edit-evento.tsx b/src/containers/edit-evento.tsx index bc30df1..67172d1 100644 --- a/src/containers/edit-evento.tsx +++ b/src/containers/edit-evento.tsx @@ -4,7 +4,7 @@ import Button from '@/components/button'; import Input from '@/components/input'; import Select from '@/components/select'; import { CreateEventoType } from '@/types/create-evento'; -import { GetEventoWithCuestionario } from '@/types/evento'; +import { GetEventoWithCuestionarios } from '@/types/evento'; import axiosInstance from '@/utils/api-config'; import { getAxiosError } from '@/utils/errors-utils'; import { commands } from '@uiw/react-md-editor'; @@ -14,9 +14,9 @@ import toast from 'react-hot-toast'; const MDEditor = dynamic(() => import('@uiw/react-md-editor'), { ssr: false }); interface EditEventoProps { - evento: GetEventoWithCuestionario; + evento: GetEventoWithCuestionarios; handleChange: (field: keyof CreateEventoType, value: string | Date) => void; - handleOnChange: (eventoActualizado: GetEventoWithCuestionario) => void; + handleOnChange: (eventoActualizado: GetEventoWithCuestionarios) => void; } export default function EditEvento({ @@ -95,7 +95,7 @@ export default function EditEvento({
{ const texto = value || ''; if (texto.length <= 500) { @@ -117,6 +117,14 @@ export default function EditEvento({ { label: 'Conferencia', value: 'conferencia' }, { label: 'Taller', value: 'taller' }, { label: 'Seminario', value: 'seminario' }, + { label: 'Curso', value: 'curso' }, + { label: 'Webinar', value: 'webinar' }, + { label: 'Reunión', value: 'reunion' }, + { label: 'Feria', value: 'feria' }, + { label: 'Concierto', value: 'concierto' }, + { label: 'Exposición', value: 'exposicion' }, + { label: 'Deportivo', value: 'deportivo' }, + { label: 'Cultural', value: 'cultural' }, { label: 'Otro', value: 'otro' }, ]} /> diff --git a/src/types/create-evento.d.ts b/src/types/create-evento.d.ts index edee5f4..b553fda 100644 --- a/src/types/create-evento.d.ts +++ b/src/types/create-evento.d.ts @@ -4,4 +4,5 @@ export interface CreateEventoType { descripcion_evento?: string; // Es opciona fecha_inicio: Date; fecha_fin: Date; + cupo_maximo?: number; // Es opcional } diff --git a/src/types/evento.d.ts b/src/types/evento.d.ts index dddd4e2..b6f1a35 100644 --- a/src/types/evento.d.ts +++ b/src/types/evento.d.ts @@ -23,12 +23,18 @@ export interface Cuestionario { fecha_fin: string; // formato ISO 8601 id_tipo_cuestionario: number; id_evento: number; + cupo_maximo: number | null; tipoCuestionario: { id_tipo_cuestionario: number; tipo_cuestionario: string; }; } +export interface CuestionarioWithCupo extends Cuestionario { + cupos_usados: number; + cupos_disponibles: number; +} + export interface GetCuestionario { tipo_cuestionario: { id_tipo_cuestionario: number; @@ -77,7 +83,7 @@ export interface CuestionarioWithSecciones extends Cuestionario { } export interface GetEventoWithCuestionarios extends GetEvento { - cuestionarios: Cuestionario[]; + cuestionarios: CuestionarioWithCupo[]; } export interface GetEventoWithCuestionario extends GetEvento {