feat: add markdown editor for event description and update event forms
- Added @uiw/react-md-editor dependency for rich text editing. - Implemented dynamic loading of the markdown editor in create and edit event forms. - Updated event forms to include new fields for 'apellidos', 'genero', and 'rfc'. - Refactored event and questionnaire pages to improve data fetching and rendering. - Removed unnecessary Excel files from the public directory. - Enhanced error handling and logging in event creation and form submission processes. - Updated carousel component to handle empty image arrays gracefully. - Created a new FormularioPage component to streamline questionnaire rendering.
This commit is contained in:
Generated
+1013
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,7 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@uiw/react-md-editor": "^4.0.7",
|
||||
"@yudiel/react-qr-scanner": "^2.2.1",
|
||||
"axios": "^1.8.4",
|
||||
"bootstrap": "^5.3.3",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -11,6 +11,7 @@ import Button from '@/components/button';
|
||||
import axiosInstance from '@/utils/api-config';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { getAxiosError } from '@/utils/errors-utils';
|
||||
|
||||
export default function Page() {
|
||||
const [eventoBanner, setEventoBanner] = useState<File | null>(null);
|
||||
@@ -69,6 +70,10 @@ export default function Page() {
|
||||
|
||||
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,
|
||||
...datosFormulario,
|
||||
@@ -77,10 +82,11 @@ export default function Page() {
|
||||
console.log('Formulario creado:', createFormulario.data);
|
||||
}
|
||||
|
||||
router.push(`/administrador`)
|
||||
router.push(`/administrador`);
|
||||
} catch (error) {
|
||||
const msg = getAxiosError(error);
|
||||
toast.error(
|
||||
'Error al crear el evento y el formulario. Por favor, inténtalo de nuevo.'
|
||||
`Error al crear el evento o formulario: ${msg.message || 'Error desconocido'}`
|
||||
);
|
||||
console.error('Error al crear evento y formulario:', error);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
'use client';
|
||||
import MarkdownRenderer from '@/components/markdown-render';
|
||||
import FormularioRegistro from '@/containers/formulario/formulario-registro';
|
||||
import { useGetApi } from '@/hooks/use-get-api';
|
||||
import FormularioPage from '@/containers/pages/formulario-page';
|
||||
import { GetEventoWithCuestionario } from '@/types/evento';
|
||||
import Image from 'next/image';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { Metadata } from 'next';
|
||||
import React from 'react';
|
||||
|
||||
type Params = {
|
||||
@@ -13,36 +9,40 @@ type Params = {
|
||||
id_cuestionario: string;
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
const params = useParams<Params>();
|
||||
const { data, error } = useGetApi<GetEventoWithCuestionario>(
|
||||
`/evento/${params.id_evento}/cuestionario/${params.id_cuestionario}`
|
||||
type Props = {
|
||||
params: Promise<Params>;
|
||||
};
|
||||
|
||||
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||
const { id_evento, id_cuestionario } = await params;
|
||||
|
||||
const response = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/evento/${id_evento}/cuestionario/${id_cuestionario}`
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return <div>Error al cargar el cuestionario: {error.message}</div>;
|
||||
}
|
||||
const data: GetEventoWithCuestionario = await response.json();
|
||||
|
||||
return {
|
||||
title: data?.nombre_evento || 'Evento',
|
||||
description: data?.descripcion_evento || 'Descripción del evento',
|
||||
openGraph: {
|
||||
title: data?.nombre_evento || 'Evento',
|
||||
description: data?.descripcion_evento || 'Descripción del evento',
|
||||
images: [
|
||||
{
|
||||
url: `${process.env.NEXT_PUBLIC_API_URL}/banners/${data.banner}`,
|
||||
width: 800,
|
||||
height: 400,
|
||||
alt: data?.nombre_evento || 'Evento',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default async function Page({ params }: Props) {
|
||||
const { id_evento, id_cuestionario } = await params;
|
||||
return (
|
||||
<div className="container my-5">
|
||||
<div className="text-center mb-4">
|
||||
{data?.banner && (
|
||||
<Image
|
||||
src={`${process.env.NEXT_PUBLIC_API_URL}/banners/${data.banner}`}
|
||||
alt={data?.nombre_evento || ''}
|
||||
width={800}
|
||||
height={400}
|
||||
className="img-fluid rounded"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<h1>{data?.nombre_evento}</h1>
|
||||
{data?.descripcion_evento && (
|
||||
<MarkdownRenderer markdown={data?.descripcion_evento} />
|
||||
)}
|
||||
{data && (
|
||||
<FormularioRegistro id_cuestionario={data?.cuestionario.id_cuestionario} />
|
||||
)}
|
||||
</div>
|
||||
<FormularioPage id_evento={id_evento} id_cuestionario={id_cuestionario} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,11 +9,9 @@ export interface CarouselProps {
|
||||
}[];
|
||||
}
|
||||
|
||||
export default function Carousel({ id, images }: CarouselProps) {
|
||||
console.log('Carousel images:', images);
|
||||
|
||||
export default function Carousel({ id="carousel-banners-eventos", images }: CarouselProps) {
|
||||
if (!images || images.length === 0) {
|
||||
return <div>No hay imágenes para mostrar</div>;
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
'use client';
|
||||
import ImageUploader from '@/components/banner-uploader';
|
||||
import Input from '@/components/input';
|
||||
import Select from '@/components/select';
|
||||
import { CreateEventoType } from '@/types/create-evento';
|
||||
import { commands } from '@uiw/react-md-editor';
|
||||
import dynamic from 'next/dynamic';
|
||||
import React from 'react';
|
||||
|
||||
// Carga dinámica para evitar problemas con SSR
|
||||
const MDEditor = dynamic(() => import('@uiw/react-md-editor'), { ssr: false });
|
||||
const MAX_LENGTH = 500;
|
||||
|
||||
interface CreateEventoProps {
|
||||
evento: CreateEventoType;
|
||||
handleChange: (field: keyof CreateEventoType, value: string | Date) => void;
|
||||
@@ -17,13 +24,14 @@ export default function CreateEvento({
|
||||
}: CreateEventoProps) {
|
||||
return (
|
||||
<div className="row border p-3 rounded my-4">
|
||||
<h2>
|
||||
Informacion del Evento
|
||||
</h2>
|
||||
<h2>Información del Evento</h2>
|
||||
<p>
|
||||
Completa la siguiente informacion para describir la informacion general del evento
|
||||
Completa la siguiente información para describir los detalles generales
|
||||
del evento.
|
||||
</p>
|
||||
|
||||
<ImageUploader label="Banner del evento" onChange={handleBannerChange} />
|
||||
|
||||
<Input
|
||||
label="Nombre del evento"
|
||||
required
|
||||
@@ -31,15 +39,21 @@ export default function CreateEvento({
|
||||
onChange={(e) => handleChange('nombre_evento', e.target.value)}
|
||||
/>
|
||||
|
||||
<div className="mb-3">
|
||||
<div className="mb-4">
|
||||
<label className="form-label">Descripción del evento</label>
|
||||
<textarea
|
||||
className="form-control bg-white"
|
||||
rows={3}
|
||||
placeholder="Describe el evento..."
|
||||
value={evento.descripcion_evento}
|
||||
onChange={(e) => handleChange('descripcion_evento', e.target.value)}
|
||||
/>
|
||||
<div data-color-mode="light">
|
||||
<MDEditor
|
||||
value={evento.descripcion_evento}
|
||||
onChange={(value) => {
|
||||
const texto = value || '';
|
||||
if (texto.length <= MAX_LENGTH) {
|
||||
handleChange('descripcion_evento', texto);
|
||||
}
|
||||
}}
|
||||
height={300}
|
||||
commands={[commands.bold, commands.italic, commands.hr]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Select
|
||||
@@ -66,6 +80,7 @@ export default function CreateEvento({
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-md-6">
|
||||
<Input
|
||||
label="Fecha de fin"
|
||||
|
||||
@@ -25,12 +25,15 @@ const tiposValidacion: TiposValidacion[] = [
|
||||
'correo_institucional',
|
||||
'telefono',
|
||||
'nombre',
|
||||
'apellidos',
|
||||
'genero',
|
||||
'entero',
|
||||
'decimal',
|
||||
'comunidad_alumno',
|
||||
'cuenta_alumno',
|
||||
'comunidad_trabajador',
|
||||
'cuenta_trabajador',
|
||||
'rfc'
|
||||
];
|
||||
|
||||
type CampoPreguntaSimple =
|
||||
|
||||
@@ -7,8 +7,11 @@ import { CreateEventoType } from '@/types/create-evento';
|
||||
import { GetEventoWithCuestionario } from '@/types/evento';
|
||||
import axiosInstance from '@/utils/api-config';
|
||||
import { getAxiosError } from '@/utils/errors-utils';
|
||||
import { commands } from '@uiw/react-md-editor';
|
||||
import dynamic from 'next/dynamic';
|
||||
import React, { useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
const MDEditor = dynamic(() => import('@uiw/react-md-editor'), { ssr: false });
|
||||
|
||||
interface EditEventoProps {
|
||||
evento: GetEventoWithCuestionario;
|
||||
@@ -88,15 +91,21 @@ export default function EditEvento({
|
||||
onChange={(e) => handleChange('nombre_evento', e.target.value)}
|
||||
/>
|
||||
|
||||
<div className="mb-3">
|
||||
<div className="mb-4">
|
||||
<label className="form-label">Descripción del evento</label>
|
||||
<textarea
|
||||
className="form-control bg-white"
|
||||
rows={5}
|
||||
placeholder="Describe el evento..."
|
||||
value={evento.descripcion_evento}
|
||||
onChange={(e) => handleChange('descripcion_evento', e.target.value)}
|
||||
/>
|
||||
<div data-color-mode="light">
|
||||
<MDEditor
|
||||
value={evento.descripcion_evento}
|
||||
onChange={(value) => {
|
||||
const texto = value || '';
|
||||
if (texto.length <= 500) {
|
||||
handleChange('descripcion_evento', texto);
|
||||
}
|
||||
}}
|
||||
height={250}
|
||||
commands={[commands.bold, commands.italic, commands.hr]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Select
|
||||
|
||||
@@ -12,11 +12,12 @@ import { useRouter } from 'next/navigation';
|
||||
import { getAxiosError } from '@/utils/errors-utils';
|
||||
|
||||
type UsuarioData = {
|
||||
nombre: string;
|
||||
apellidos: string;
|
||||
correo: string;
|
||||
genero: 'M' | 'F';
|
||||
carrera: string;
|
||||
nombre: string; // alumno o trabajador
|
||||
apellidos: string; // alumno o trabajador
|
||||
correo: string; // alumno o trabajador (en los alumnos se usa el id_ncuenta en trabajadores ellos deben escribirlo)
|
||||
genero: 'M' | 'F'; // alumno o trabajador
|
||||
carrera: string; // solo para alumnos
|
||||
rfc?: string; // solo para trabajadores se usa para buscar información de cuenta
|
||||
};
|
||||
|
||||
const confirmativeOption = ['Sí', 'Si', 'Si, claro', 'Claro que sí'];
|
||||
@@ -60,7 +61,8 @@ export default function FormularioRegistro({
|
||||
const preguntaCuenta = preguntas?.find(
|
||||
(pregunta) =>
|
||||
pregunta.validacion === 'cuenta_alumno' ||
|
||||
pregunta.validacion === 'cuenta_trabajador'
|
||||
pregunta.validacion === 'cuenta_trabajador' ||
|
||||
pregunta.validacion === 'rfc'
|
||||
);
|
||||
|
||||
// ------------------------
|
||||
@@ -71,12 +73,13 @@ export default function FormularioRegistro({
|
||||
|
||||
const esCuentaAlumno = preguntaCuenta?.validacion === 'cuenta_alumno';
|
||||
const esCuentaTrabajador =
|
||||
preguntaCuenta?.validacion === 'cuenta_trabajador';
|
||||
preguntaCuenta?.validacion === 'cuenta_trabajador' ||
|
||||
preguntaCuenta?.validacion === 'rfc';
|
||||
|
||||
const puedeBuscar =
|
||||
cuenta &&
|
||||
typeof cuenta === 'string' &&
|
||||
((esCuentaAlumno && cuenta.length === 9) || esCuentaTrabajador);
|
||||
((esCuentaAlumno && cuenta.length === 9) || (esCuentaTrabajador && cuenta.length === 5));
|
||||
|
||||
const fetchCuentaInfo = async () => {
|
||||
try {
|
||||
@@ -85,17 +88,22 @@ export default function FormularioRegistro({
|
||||
: `/trabajadores/${cuenta}`;
|
||||
|
||||
const res = await axiosInstance.get(endpoint);
|
||||
console.log('Datos de cuenta obtenidos:', res.data);
|
||||
|
||||
// Usar datos fake para pruebas
|
||||
// Usar datos fake para pruebas
|
||||
setCuentaInfo({
|
||||
nombre: res.data.nombre,
|
||||
apellidos: res.data.apellidos,
|
||||
correo: '',
|
||||
genero: res.data.genero,
|
||||
carrera: res.data.carrera ? res.data.carrera : '',
|
||||
});
|
||||
// Usar datos fake para pruebas
|
||||
/* setCuentaInfo({
|
||||
nombre: 'Juan',
|
||||
apellidos: 'Pérez',
|
||||
correo: 'juan.perez@pcpuma.acatlan.unam.mx',
|
||||
genero: 'M',
|
||||
carrera: 'Ingeniería en Computación',
|
||||
});
|
||||
}); */
|
||||
} catch (error) {
|
||||
console.error('Error al obtener datos de cuenta:', error);
|
||||
setCuentaInfo(null);
|
||||
@@ -358,6 +366,7 @@ export default function FormularioRegistro({
|
||||
cuentaInfo={cuentaInfo}
|
||||
isComunidad={isComunidad?.label}
|
||||
onChange={actualizarRespuesta}
|
||||
preguntaComunidad={preguntaComunidad?.validacion ?? ''}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ type Props = {
|
||||
} | null;
|
||||
isComunidad: string | undefined;
|
||||
onChange: (id: string, valor: string) => void;
|
||||
preguntaComunidad: string;
|
||||
};
|
||||
|
||||
const confirmativeOption = ['Sí', 'Si', 'Si, claro', 'Claro que sí'];
|
||||
@@ -30,28 +31,32 @@ export default function PrefetchAbiertaCorta({
|
||||
isComunidad,
|
||||
obligatorio,
|
||||
onChange,
|
||||
preguntaComunidad,
|
||||
}: Props) {
|
||||
console.log(preguntaComunidad);
|
||||
let defaultValue = '';
|
||||
let disabled = false;
|
||||
|
||||
if (confirmativeOption.includes(isComunidad ?? '') && cuentaInfo) {
|
||||
if (validacion === 'nombre') {
|
||||
if (validacion === 'nombre' && cuentaInfo.nombre) {
|
||||
defaultValue = cuentaInfo.nombre;
|
||||
disabled = true;
|
||||
}
|
||||
if (validacion === 'apellidos') {
|
||||
if (validacion === 'apellidos' && cuentaInfo.apellidos) {
|
||||
defaultValue = cuentaInfo.apellidos;
|
||||
disabled = true;
|
||||
}
|
||||
if (validacion === 'correo') {
|
||||
if (validacion === 'correo' && cuentaInfo.correo) {
|
||||
defaultValue = cuentaInfo.correo;
|
||||
disabled = true;
|
||||
if (preguntaComunidad !== 'comunidad_trabajador') {
|
||||
disabled = true;
|
||||
}
|
||||
}
|
||||
if (validacion === 'institucion') {
|
||||
defaultValue = 'FES Acatlán';
|
||||
disabled = true;
|
||||
}
|
||||
if (validacion === 'carrera') {
|
||||
if (validacion === 'carrera' && cuentaInfo.carrera) {
|
||||
defaultValue = cuentaInfo.carrera;
|
||||
disabled = true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
'use client'
|
||||
import MarkdownRenderer from '@/components/markdown-render';
|
||||
import { useGetApi } from '@/hooks/use-get-api';
|
||||
import Image from 'next/image';
|
||||
import React from 'react';
|
||||
import FormularioRegistro from '../formulario/formulario-registro';
|
||||
import { GetEventoWithCuestionario } from '@/types/evento';
|
||||
|
||||
export default function FormularioPage({
|
||||
id_evento,
|
||||
id_cuestionario,
|
||||
}: {
|
||||
id_evento: string;
|
||||
id_cuestionario: string;
|
||||
}) {
|
||||
const { data, error } = useGetApi<GetEventoWithCuestionario>(
|
||||
`/evento/${id_evento}/cuestionario/${id_cuestionario}`
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return <div>Error al cargar el cuestionario: {error.message}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container my-5">
|
||||
<div className="text-center mb-4">
|
||||
{data?.banner && (
|
||||
<Image
|
||||
src={`${process.env.NEXT_PUBLIC_API_URL}/banners/${data.banner}`}
|
||||
alt={data?.nombre_evento || ''}
|
||||
width={800}
|
||||
height={400}
|
||||
className="img-fluid rounded"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<h1>{data?.nombre_evento}</h1>
|
||||
{data?.descripcion_evento && (
|
||||
<MarkdownRenderer markdown={data?.descripcion_evento} />
|
||||
)}
|
||||
{data && (
|
||||
<FormularioRegistro
|
||||
id_cuestionario={data?.cuestionario.id_cuestionario}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+12
-2
@@ -13,7 +13,6 @@ export const plantillasDisponibles: Plantilla[] = [
|
||||
nombre: 'Registro Comunidad Trabajador',
|
||||
id: 'registro-comunidad-estudiantil',
|
||||
datos: {
|
||||
id_evento: 1,
|
||||
nombre_form: 'Registro',
|
||||
descripcion:
|
||||
'Con este formulario estaras reservando tu lugar en el evento. Por favor, completa todos los campos obligatorios.',
|
||||
@@ -78,7 +77,6 @@ export const plantillasDisponibles: Plantilla[] = [
|
||||
nombre: 'Registro Comunidad Alumnos',
|
||||
id: 'registro-comunidad-alumnos',
|
||||
datos: {
|
||||
id_evento: 1,
|
||||
nombre_form: 'Registro',
|
||||
descripcion:
|
||||
'Con este formulario estaras reservando tu lugar en el evento. Por favor, completa todos los campos obligatorios.',
|
||||
@@ -151,3 +149,15 @@ export const plantillasDisponibles: Plantilla[] = [
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/*
|
||||
¡Bienvenidas y bienvenidos a la Facultad!
|
||||
Inicia esta nueva etapa con un recorrido especial por nuestras instalaciones, pensado para que conozcas tu nueva casa universitaria. Te acompañaremos por aulas, laboratorios, bibliotecas, áreas comunes y servicios que estarán a tu disposición durante tu formación. Además, podrás resolver dudas y conocer a quienes serán parte de tu camino académico.
|
||||
¡No faltes, esta actividad marcará el inicio de grandes experiencias!
|
||||
*/
|
||||
|
||||
/*
|
||||
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.
|
||||
¡Les esperamos con gusto para comenzar juntos esta nueva etapa profesional!
|
||||
*/
|
||||
Vendored
-1
@@ -22,7 +22,6 @@ export type TipoPregunta =
|
||||
| 'Multiple';
|
||||
|
||||
export interface FormularioCreacion {
|
||||
id_evento: number;
|
||||
nombre_form: string;
|
||||
descripcion: string;
|
||||
fecha_inicio: string; // ISO 8601 date string
|
||||
|
||||
Reference in New Issue
Block a user