feat: implement event and questionnaire creation with updated forms; add editing capabilities for questionnaires and events
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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<Params>();
|
||||
|
||||
const [cuestionario, setCuestionario] =
|
||||
React.useState<GetCuestionario | null>(null);
|
||||
const [busquedaCorreo, setBusquedaCorreo] = React.useState('');
|
||||
const [loadingAsistencia, setLoadingAsistencia] = React.useState<
|
||||
Record<number, boolean>
|
||||
@@ -25,10 +29,14 @@ export default function Page() {
|
||||
const { data: participantes, setData } = useGetApi<ParticipacionEvento[]>(
|
||||
`/participante-evento/evento/${params.id_cuestionario}`
|
||||
);
|
||||
const { data: cuestionario } = useGetApi<GetCuestionario>(
|
||||
const { data } = useGetApi<GetCuestionario>(
|
||||
`/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 (
|
||||
<div>
|
||||
<h2 className="mt-4">{cuestionario?.nombre_form}</h2>
|
||||
<p className="text-muted mb-4">
|
||||
Aquí puedes ver los participantes registrados y confirmar su asistencia.
|
||||
</p>
|
||||
<div className="my-4">
|
||||
<Link
|
||||
href={`/administrador/evento/${params.id_evento}`}
|
||||
className="btn btn-link mb-3"
|
||||
@@ -128,6 +149,23 @@ export default function Page() {
|
||||
<i className="bi bi-arrow-left me-2"></i>
|
||||
Volver
|
||||
</Link>
|
||||
|
||||
<h2 className="mb-4">Formulario</h2>
|
||||
|
||||
{cuestionario && (
|
||||
<EditFormulario
|
||||
cuestionario={cuestionario}
|
||||
handleChange={handleChange}
|
||||
handleOnChange={handleCuestionarioActualizado}
|
||||
/>
|
||||
)}
|
||||
|
||||
<h3 className="mt-5">Participantes</h3>
|
||||
|
||||
{participantes?.length === 0 && (
|
||||
<p className="text-muted">No hay participantes registrados aún.</p>
|
||||
)}
|
||||
|
||||
{participantes && participantes.length > 0 && (
|
||||
<>
|
||||
<SimpleInput
|
||||
|
||||
@@ -15,7 +15,6 @@ import Button from '@/components/button';
|
||||
import { downloadFile } from '@/utils/downloas-utils';
|
||||
import Link from 'next/link';
|
||||
|
||||
|
||||
type Params = {
|
||||
id_evento: string;
|
||||
};
|
||||
@@ -133,7 +132,15 @@ export default function Page() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container my-4">
|
||||
<div className="my-4">
|
||||
<Link
|
||||
href={`/administrador`}
|
||||
className="btn btn-link mb-3"
|
||||
>
|
||||
<i className="bi bi-arrow-left me-2"></i>
|
||||
Volver
|
||||
</Link>
|
||||
|
||||
<h2 className="mb-4">Evento</h2>
|
||||
|
||||
<EditEvento
|
||||
|
||||
@@ -49,7 +49,7 @@ export default function EventoCard({
|
||||
<div className="card-body">
|
||||
<div className="card-description mb-0">
|
||||
{evento.cuestionarios[0]?.cupo_maximo === null ? (
|
||||
<span className="badge bg-success">Sin límite</span>
|
||||
<span className="badge bg-success mb-2">Sin límite</span>
|
||||
) : (
|
||||
<span className="badge bg-primary mb-2">
|
||||
{evento.cuestionarios[0]?.cupos_disponibles} cupos disponibles
|
||||
|
||||
@@ -33,24 +33,12 @@ export default function CreateEvento({
|
||||
|
||||
<ImageUploader label="Banner del evento" onChange={handleBannerChange} />
|
||||
|
||||
<div className="col-md-8">
|
||||
<Input
|
||||
label="Nombre del evento"
|
||||
required
|
||||
value={evento.nombre_evento}
|
||||
onChange={(e) => handleChange('nombre_evento', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-md-4">
|
||||
<Input
|
||||
label="Cupo máximo"
|
||||
type='number'
|
||||
required
|
||||
value={evento.cupo_maximo}
|
||||
onChange={(e) => handleChange('cupo_maximo', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
label="Nombre del evento"
|
||||
required
|
||||
value={evento.nombre_evento}
|
||||
onChange={(e) => handleChange('nombre_evento', e.target.value)}
|
||||
/>
|
||||
|
||||
<div className="mb-4">
|
||||
<label className="form-label">Descripción del evento</label>
|
||||
|
||||
@@ -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 (
|
||||
<div className="border p-3 rounded my-4">
|
||||
<div className="row border p-3 rounded my-4">
|
||||
<h2 className="mb-3">Editar Formulario</h2>
|
||||
|
||||
<SimpleInput
|
||||
label="Nombre del Formulario"
|
||||
value={formulario.nombre_form}
|
||||
onChange={(e) => actualizarCampo('nombre_form', e.target.value)}
|
||||
/>
|
||||
<div className="col-md-8">
|
||||
<SimpleInput
|
||||
label="Nombre del Formulario"
|
||||
value={formulario.nombre_form}
|
||||
onChange={(e) => actualizarCampo('nombre_form', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-md-4">
|
||||
<SimpleInput
|
||||
label="Cupo máximo (Si no se requiere, dejar en blanco)"
|
||||
type="number"
|
||||
required
|
||||
value={formulario.cupo_maximo}
|
||||
onChange={(e) =>
|
||||
actualizarCampo('cupo_maximo', Number(e.target.value))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SimpleInput
|
||||
label="Descripción del Formulario"
|
||||
|
||||
@@ -6,6 +6,7 @@ import Select from '@/components/select';
|
||||
import { CreateEventoType } from '@/types/create-evento';
|
||||
import { GetEventoWithCuestionarios } from '@/types/evento';
|
||||
import axiosInstance from '@/utils/api-config';
|
||||
import { formatDateLocal } from '@/utils/date-utils';
|
||||
import { getAxiosError } from '@/utils/errors-utils';
|
||||
import { commands } from '@uiw/react-md-editor';
|
||||
import dynamic from 'next/dynamic';
|
||||
@@ -24,7 +25,6 @@ export default function EditEvento({
|
||||
handleChange,
|
||||
handleOnChange,
|
||||
}: EditEventoProps) {
|
||||
const toDateInputValue = (isoString: string) => isoString.slice(0, 10);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [nuevoBanner, setNuevoBanner] = useState<File | null>(null);
|
||||
@@ -132,20 +132,22 @@ export default function EditEvento({
|
||||
<div className="col-md-6">
|
||||
<Input
|
||||
label="Fecha de inicio"
|
||||
type="date"
|
||||
type="datetime-local"
|
||||
className={{ container: 'mb-3' }}
|
||||
value={toDateInputValue(evento.fecha_inicio)}
|
||||
onChange={(e) => handleChange('fecha_inicio', e.target.value)}
|
||||
value={formatDateLocal(new Date(evento.fecha_inicio))}
|
||||
onChange={(e) =>
|
||||
handleChange('fecha_inicio', new Date(e.target.value))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-md-6">
|
||||
<Input
|
||||
label="Fecha de fin"
|
||||
type="date"
|
||||
type="datetime-local"
|
||||
className={{ container: 'mb-3' }}
|
||||
value={toDateInputValue(evento.fecha_fin)}
|
||||
onChange={(e) => handleChange('fecha_fin', e.target.value)}
|
||||
value={formatDateLocal(new Date(evento.fecha_fin))}
|
||||
onChange={(e) => handleChange('fecha_fin', new Date(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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<File | null>(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 (
|
||||
<div className="row border p-3 rounded my-4">
|
||||
<h2>Editar Información del formulario</h2>
|
||||
<p>Modifica los datos del evento que desees actualizar.</p>
|
||||
|
||||
<ImageUploader
|
||||
label="Banner del formulario"
|
||||
defaultBanner={
|
||||
cuestionario.banner
|
||||
? `${process.env.NEXT_PUBLIC_API_URL}/banners/${cuestionario.banner}`
|
||||
: undefined
|
||||
}
|
||||
onChange={(file) => {
|
||||
setNuevoBanner(file);
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="col-md-8">
|
||||
<SimpleInput
|
||||
label="Nombre del formulario"
|
||||
required
|
||||
value={cuestionario.nombre_form}
|
||||
onChange={(e) => handleChange('nombre_form', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-4">
|
||||
<SimpleInput
|
||||
label="Cupo máximo (Si no se requiere, dejar en blanco)"
|
||||
type="number"
|
||||
value={cuestionario.cupo_maximo ?? ''}
|
||||
onChange={(e) =>
|
||||
handleChange('cupo_maximo', e.target.value ? e.target.value : '')
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<label className="form-label">Descripción del evento</label>
|
||||
<div data-color-mode="light">
|
||||
<MDEditor
|
||||
value={cuestionario.descripcion ?? ''}
|
||||
onChange={(value) => {
|
||||
const texto = value || '';
|
||||
if (texto.length <= 500) {
|
||||
handleChange(
|
||||
'descripcion_evento' as keyof GetCuestionario,
|
||||
texto
|
||||
);
|
||||
}
|
||||
}}
|
||||
height={250}
|
||||
commands={[commands.bold, commands.italic, commands.hr]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-md-6">
|
||||
<SimpleInput
|
||||
label="Fecha de inicio"
|
||||
type="datetime-local"
|
||||
className={{ container: 'mb-3' }}
|
||||
value={formatDateLocal(new Date(cuestionario.fecha_inicio))}
|
||||
onChange={(e) =>
|
||||
handleChange('fecha_inicio', new Date(e.target.value))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-md-6">
|
||||
<SimpleInput
|
||||
label="Fecha de fin"
|
||||
type="datetime-local"
|
||||
className={{ container: 'mb-3' }}
|
||||
value={formatDateLocal(new Date(cuestionario.fecha_fin))}
|
||||
onChange={(e) => handleChange('fecha_fin', new Date(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button icon="save" disabled={loading} onClick={handleOnSave}>
|
||||
{loading ? 'Guardando...' : 'Guardar Cambios'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
*/
|
||||
|
||||
Vendored
-1
@@ -4,5 +4,4 @@ export interface CreateEventoType {
|
||||
descripcion_evento?: string; // Es opciona
|
||||
fecha_inicio: Date;
|
||||
fecha_fin: Date;
|
||||
cupo_maximo?: number; // Es opcional
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -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[];
|
||||
}
|
||||
|
||||
|
||||
Vendored
+2
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user