feat: enhance event forms with date formatting and registration link updates; add utility for local date formatting

This commit is contained in:
miguel
2025-06-19 18:35:15 -06:00
parent e092eeec4d
commit f85c3a35df
7 changed files with 184 additions and 27 deletions
+6 -4
View File
@@ -62,13 +62,15 @@ export default function EventoCard({
<div key={cuestionario.id_cuestionario} className="">
<Link
href={
user === 'public'
? `/evento/${evento.nombre_evento.split(' ').join('_')}/${evento.id_evento}/${cuestionario.id_cuestionario}`
: `/${user}/evento/${evento.id_evento}/${cuestionario.id_cuestionario}`
user === 'public'
? `/evento/${evento.nombre_evento.split(' ').join('_')}/${
evento.id_evento
}/${cuestionario.id_cuestionario}`
: `/${user}/evento/${evento.id_evento}/${cuestionario.id_cuestionario}`
}
className="btn mt-2 w-100 btn-outline-primary"
>
{cuestionario.nombre_form}
Regístrate
</Link>
</div>
))}
+8 -5
View File
@@ -3,6 +3,7 @@ import ImageUploader from '@/components/banner-uploader';
import Input from '@/components/input';
import Select from '@/components/select';
import { CreateEventoType } from '@/types/create-evento';
import { formatDateLocal } from '@/utils/date-utils';
import { commands } from '@uiw/react-md-editor';
import dynamic from 'next/dynamic';
import React from 'react';
@@ -72,9 +73,9 @@ export default function CreateEvento({
<div className="col-md-6">
<Input
label="Fecha de inicio"
type="date"
type="datetime-local"
className={{ container: 'mb-3' }}
value={evento.fecha_inicio.toISOString().split('T')[0]}
value={formatDateLocal(evento.fecha_inicio)}
onChange={(e) =>
handleChange('fecha_inicio', new Date(e.target.value))
}
@@ -84,10 +85,12 @@ export default function CreateEvento({
<div className="col-md-6">
<Input
label="Fecha de fin"
type="date"
type="datetime-local"
className={{ container: 'mb-3' }}
value={evento.fecha_fin.toISOString().split('T')[0]}
onChange={(e) => handleChange('fecha_fin', new Date(e.target.value))}
value={formatDateLocal(evento.fecha_fin)}
onChange={(e) =>
handleChange('fecha_fin', new Date(e.target.value))
}
/>
</div>
</div>
+2 -2
View File
@@ -159,7 +159,7 @@ export default function FormularioEditor({ formulario, onChange }: Props) {
<div className="col-md-6">
<SimpleInput
label="Fecha de Inicio"
type="datetime-local"
type="date"
value={formulario.fecha_inicio.slice(0, 16)}
onChange={(e) => actualizarCampo('fecha_inicio', e.target.value)}
/>
@@ -167,7 +167,7 @@ export default function FormularioEditor({ formulario, onChange }: Props) {
<div className="col-md-6">
<SimpleInput
label="Fecha de Fin"
type="datetime-local"
type="date"
value={formulario.fecha_fin.slice(0, 16)}
onChange={(e) => actualizarCampo('fecha_fin', e.target.value)}
/>
@@ -11,6 +11,15 @@ import toast from 'react-hot-toast';
import { useRouter } from 'next/navigation';
import { getAxiosError } from '@/utils/errors-utils';
export type UsuarioDataResponse = {
cuenta: string | null;
nombre: string | null;
apellidos: string | null;
carrera: string | null;
genero: string | null;
rfc?: string | null; // Solo para trabajadores
};
type UsuarioData = {
nombre: string; // alumno o trabajador
apellidos: string; // alumno o trabajador
@@ -79,7 +88,8 @@ export default function FormularioRegistro({
const puedeBuscar =
cuenta &&
typeof cuenta === 'string' &&
((esCuentaAlumno && cuenta.length === 9) || (esCuentaTrabajador && cuenta.length === 5));
((esCuentaAlumno && cuenta.length === 9) ||
(esCuentaTrabajador && cuenta.length === 10));
const fetchCuentaInfo = async () => {
try {
@@ -87,14 +97,20 @@ export default function FormularioRegistro({
? `/alumnos/${cuenta}`
: `/trabajadores/${cuenta}`;
const res = await axiosInstance.get(endpoint);
const res = await axiosInstance.get<UsuarioDataResponse>(endpoint);
console.log('Datos de cuenta obtenidos:', res.data);
setCuentaInfo({
nombre: res.data.nombre,
apellidos: res.data.apellidos,
correo: '',
genero: res.data.genero,
carrera: res.data.carrera ? res.data.carrera : '',
nombre: res.data.nombre ? res.data.nombre : '',
apellidos: res.data.apellidos ? res.data.apellidos : '',
correo: res.data.cuenta
? `${res.data.cuenta}@pcpuma.acatlan.unam.mx`
: '',
genero:
res.data.genero === 'M' || res.data.genero === 'F'
? res.data.genero
: 'M',
carrera: res.data.carrera ? res.data.carrera : '',
});
// Usar datos fake para pruebas
/* setCuentaInfo({
@@ -197,6 +213,7 @@ 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');
@@ -217,13 +234,22 @@ export default function FormularioRegistro({
respuestas[pregunta.id_pregunta] === null
);
const preguntaCorreo = preguntasObligatorias.find(
(pregunta) =>
pregunta.validacion === 'correo' ||
pregunta.validacion === 'correo_institucional'
);
if (faltantes.length > 0) {
toast.error('Por favor responde todas las preguntas obligatorias.');
return;
}
// Buscar un posible correo en las respuestas si no hay cuentaInfo
let correo = cuentaInfo?.correo;
let correo = cuentaInfo?.correo === respuestas[preguntaCorreo?.id_pregunta ?? '']
? cuentaInfo?.correo
: respuestas[preguntaCorreo?.id_pregunta ?? ''];
if (!correo) {
// Buscar en las respuestas una que parezca correo
const correoRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
@@ -245,8 +271,6 @@ export default function FormularioRegistro({
})),
};
console.log('Payload para enviar:', payload);
try {
const res = await axiosInstance.post(
'/cuestionario-respondido/submit',
@@ -473,7 +497,7 @@ export default function FormularioRegistro({
<Button
className="btn btn-primary"
onClick={handleOnSubmit}
disabled={!mostrarFormularioRestante}
disabled={!mostrarFormularioRestante || isSending}
>
{isSending ? 'Enviando...' : 'Enviar Formulario'}
</Button>
+21 -1
View File
@@ -1,10 +1,11 @@
'use client'
'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';
import { formatFecha } from '@/utils/date-utils';
export default function FormularioPage({
id_evento,
@@ -38,6 +39,25 @@ export default function FormularioPage({
{data?.descripcion_evento && (
<MarkdownRenderer markdown={data?.descripcion_evento} />
)}
{data?.fecha_fin && data?.fecha_inicio && (
<>
<p className="mb-0 mt-2 text-muted">Horario:</p>
<p className="text-muted">
<i className="bi bi-clock me-1 text-primary"></i>
{formatFecha(data.fecha_inicio!, {
horas: true,
minutos: true,
})}{' '}
al{' '}
{formatFecha(data.fecha_fin!, {
horas: true,
minutos: true,
})}
</p>
</>
)}
{data && (
<FormularioRegistro
id_cuestionario={data?.cuestionario.id_cuestionario}
+10 -4
View File
@@ -48,13 +48,13 @@ export const plantillasDisponibles: Plantilla[] = [
titulo: 'Nombre(s)',
tipo: 'Abierta (Respuesta corta)',
obligatoria: true,
validacion: 'nombre'
validacion: 'nombre',
},
{
titulo: 'Apellidos',
tipo: 'Abierta (Respuesta corta)',
obligatoria: true,
validacion: 'apellidos'
validacion: 'apellidos',
},
{
titulo: 'Género',
@@ -67,6 +67,12 @@ export const plantillasDisponibles: Plantilla[] = [
{ valor: 'Prefiero no decirlo' },
],
},
{
titulo: 'Carrera',
tipo: 'Abierta (Respuesta corta)',
validacion: 'carrera',
obligatoria: true,
},
],
},
],
@@ -159,5 +165,5 @@ 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.
¡Les esperamos con gusto para comenzar juntos esta nueva etapa profesional!
*/
*/
+102
View File
@@ -1,4 +1,106 @@
/**
* Formatea una fecha en formato legible en español, con opción a incluir hora, minutos y día de la semana.
*
* @param {string} fechaStr - Cadena de texto representando una fecha válida (ISO 8601 o similar).
* @param {Object} [config] - Configuración opcional para mostrar hora, minutos y día de la semana.
* @param {boolean} [config.horas=false] - Indica si se debe incluir la hora.
* @param {boolean} [config.minutos=false] - Indica si se deben incluir los minutos (requiere config.horas).
* @param {boolean} [config.diaSemana=false] - Indica si se debe incluir el día de la semana.
* @returns {string} Fecha formateada en español.
* @throws {Error} Lanza un error si la fecha proporcionada no es válida.
*
* @example
* formatFecha('2025-03-25T14:30:00Z');
* // Retorna: "25 de marzo del 2025"
*
* @example
* formatFecha('2025-03-25T14:30:00Z', { horas: true });
* // Retorna: "25 de marzo del 2025 - 14"
*
* @example
* formatFecha('2025-03-25T14:30:00Z', { horas: true, minutos: true });
* // Retorna: "25 de marzo del 2025 - 14:30"
*
* @example
* formatFecha('2025-03-25T14:30:00Z', { diaSemana: true });
* // Retorna: "martes, 25 de marzo del 2025"
*/
export function formatFecha(
fechaStr: string,
config: { horas?: boolean; minutos?: boolean; diaSemana?: boolean } = {
horas: false,
minutos: false,
diaSemana: false,
}
): string {
const fecha = new Date(fechaStr);
if (isNaN(fecha.getTime())) {
throw new Error(`Fecha inválida: ${fechaStr}`);
}
const diasSemana = [
'domingo',
'lunes',
'martes',
'miércoles',
'jueves',
'viernes',
'sábado',
];
const meses = [
'enero',
'febrero',
'marzo',
'abril',
'mayo',
'junio',
'julio',
'agosto',
'septiembre',
'octubre',
'noviembre',
'diciembre',
];
const dia = fecha.getUTCDate();
const mes = meses[fecha.getUTCMonth()];
const año = fecha.getUTCFullYear();
const hora = (fecha.getUTCHours() - 6 + 24) % 24;
const minutos = fecha.getUTCMinutes().toString().padStart(2, '0');
const diaSemana = diasSemana[fecha.getUTCDay()];
let fechaFormateada = '';
if (config.diaSemana) {
fechaFormateada += `${diaSemana}, `;
}
fechaFormateada += `${dia} de ${mes} del ${año}`;
if (config.horas) {
fechaFormateada += ` - ${hora}`;
if (config.minutos) {
fechaFormateada += `:${minutos}`;
}
}
return fechaFormateada;
}
export function formatearFecha(fecha: string, tipo: 'inicio' | 'fin'): string {
const sufijo = tipo === 'inicio' ? 'T00:00:01' : 'T23:59:59';
return `${fecha}${sufijo}`;
}
export function formatDateLocal(date: Date): string {
const pad = (n: number) => n.toString().padStart(2, '0');
const yyyy = date.getFullYear();
const MM = pad(date.getMonth() + 1);
const dd = pad(date.getDate());
const hh = pad(date.getHours());
const mm = pad(date.getMinutes());
return `${yyyy}-${MM}-${dd}T${hh}:${mm}`;
}