Files
formularios_front/src/containers/formulario/formulario-registro.tsx
T
miguel d3e1441f87 feat: add bulk user upload functionality and form success page
- Implemented bulk upload for students and workers with file validation and error handling.
- Added Excel template download feature for user uploads.
- Created success page for form submissions with QR code display.
- Established context and reducer for form state management, including user search and response handling.
- Added mermaid diagrams for event registration documentation.
2026-03-08 19:18:23 -06:00

248 lines
8.4 KiB
TypeScript

import React from 'react';
import { SubmitResponse } from '@/types/submit';
import SimpleInput from '@/components/input';
import RadioOptionGroup, { RadioOption } from '@/components/radio-option-group';
import Button from '@/components/button';
import toast from 'react-hot-toast';
import { useFormulario } from '@/context/formulario';
export type UsuarioDataResponse = {
cuenta: string | null;
nombre: string | null;
apellidos: string | null;
carrera: string | null;
genero: string | null;
rfc?: string | null;
};
export default function FormularioRegistro({
handleSubmitFormulario,
}: {
evento: string;
cuestionario: string;
id_evento: number;
id_cuestionario: number;
handleSubmitFormulario: (data: SubmitResponse) => void;
}) {
const {
state,
preguntaComunidad,
preguntaCuenta,
esComunidadSi,
esExclusivo,
mostrarFormularioCompleto,
setRespuesta,
submitFormulario,
} = useFormulario();
const { data, loadingData, errorData, respuestas, isSending, errorBusqueda, loadingBusqueda } = state;
const comunidadSeleccionada = preguntaComunidad
? respuestas[preguntaComunidad.id_pregunta]
: null;
const handleOnSubmit = async () => {
if (!data?.cuestionario.id_cuestionario) {
toast.error('No se ha cargado correctamente el formulario');
return;
}
const preguntasObligatorias = data.cuestionario.secciones.flatMap((seccion) =>
seccion.preguntas
.filter((p) => p.pregunta.obligatoria)
.map((p) => p.pregunta)
);
const faltantes = preguntasObligatorias.filter(
(p) =>
respuestas[p.id_pregunta] === undefined ||
respuestas[p.id_pregunta] === '' ||
respuestas[p.id_pregunta] === null
);
if (faltantes.length > 0) {
toast.error('Por favor responde todas las preguntas obligatorias.');
return;
}
try {
const res = await submitFormulario(data.cuestionario.id_cuestionario);
toast.success(res.message || '¡Formulario enviado exitosamente!');
handleSubmitFormulario(res);
} catch {
toast.error('Hubo un error al enviar el formulario');
}
};
if (errorData) {
return <div>Error al cargar el cuestionario</div>;
}
if (loadingData || !data) {
return <div>Cargando...</div>;
}
return (
<div>
{/* Pregunta de comunidad */}
{preguntaComunidad && (
<div className="my-4">
<label className={`form-label ${preguntaComunidad.obligatoria ? 'required' : ''}`}>
{preguntaComunidad.pregunta}
</label>
<RadioOptionGroup
name="comunidad"
options={preguntaComunidad.opciones.map((op) => ({
label: op.opcion.opcion,
value: op.id_opcion,
}))}
selectedValue={comunidadSeleccionada ? Number(comunidadSeleccionada) : undefined}
onChange={(opt) =>
setRespuesta(preguntaComunidad.id_pregunta.toString(), String(opt.value))
}
/>
</div>
)}
{/* Pregunta de cuenta (si respondió "Sí" a comunidad, o en cuestionario exclusivo) */}
{(esComunidadSi || esExclusivo) && preguntaCuenta && (
<div className="my-4">
<label className={`form-label ${preguntaCuenta.obligatoria ? 'required' : ''}`}>
{preguntaCuenta.pregunta}
</label>
<SimpleInput
type="text"
name="cuenta"
placeholder={
preguntaCuenta.validacion === 'cuenta_alumno'
? 'Ingrese su número de cuenta (9 dígitos)'
: preguntaCuenta.validacion === 'cuenta_trabajador'
? 'Ingrese su número de trabajador (6 dígitos)'
: preguntaCuenta.validacion === 'rfc'
? 'Ingrese su RFC sin homoclave (10 caracteres)'
: `Ingrese su ${preguntaCuenta.pregunta}`
}
maxLength={
preguntaCuenta.validacion === 'rfc'
? 10
: preguntaCuenta.validacion === 'cuenta_alumno'
? 9
: 6
}
value={respuestas[preguntaCuenta.id_pregunta] || ''}
onChange={(e) =>
setRespuesta(preguntaCuenta.id_pregunta.toString(), e.target.value)
}
/>
{loadingBusqueda && (
<div className="text-muted small mt-1">
<span className="spinner-border spinner-border-sm me-1" />
Buscando...
</div>
)}
{errorBusqueda && (
<div className="alert alert-danger py-2 mt-2" role="alert">
<i className="bi bi-exclamation-circle me-2"></i>
{errorBusqueda}
</div>
)}
</div>
)}
{/* Resto del formulario */}
{mostrarFormularioCompleto &&
data.cuestionario.secciones.map((seccion, i) => (
<div key={i}>
{seccion.preguntas.map((pregunta) => {
if (
pregunta.pregunta.id_pregunta === preguntaComunidad?.id_pregunta ||
pregunta.pregunta.id_pregunta === preguntaCuenta?.id_pregunta
) {
return null;
}
const valor = respuestas[pregunta.pregunta.id_pregunta] || '';
if (pregunta.pregunta.tipo_pregunta.tipo_pregunta === 'Abierta (Respuesta corta)') {
return (
<div key={pregunta.pregunta.id_pregunta} className="my-4">
<label className={`form-label ${pregunta.pregunta.obligatoria ? 'required' : ''}`}>
{pregunta.pregunta.pregunta}
</label>
<SimpleInput
type="text"
name={`respuesta_${pregunta.pregunta.id_pregunta}`}
placeholder="Ingrese una respuesta"
value={valor}
onChange={(e) =>
setRespuesta(pregunta.pregunta.id_pregunta.toString(), e.target.value)
}
/>
</div>
);
}
if (pregunta.pregunta.tipo_pregunta.tipo_pregunta === 'Abierta (Parrafo)') {
return (
<div key={pregunta.pregunta.id_pregunta} className="my-4">
<label className={`form-label ${pregunta.pregunta.obligatoria ? 'required' : ''}`}>
{pregunta.pregunta.pregunta}
</label>
<SimpleInput
type="textarea"
name={`respuesta_${pregunta.pregunta.id_pregunta}`}
placeholder="Ingrese una respuesta"
value={valor}
onChange={(e) =>
setRespuesta(pregunta.pregunta.id_pregunta.toString(), e.target.value)
}
/>
</div>
);
}
if (
pregunta.pregunta.tipo_pregunta.tipo_pregunta === 'Cerrada' ||
pregunta.pregunta.tipo_pregunta.tipo_pregunta === 'Multiple'
) {
const opciones: RadioOption<number>[] = pregunta.pregunta.opciones.map((op) => ({
label: op.opcion.opcion,
value: op.id_opcion,
}));
return (
<div key={pregunta.pregunta.id_pregunta} className="my-4">
<label className={`form-label ${pregunta.pregunta.obligatoria ? 'required' : ''}`}>
{pregunta.pregunta.pregunta}
</label>
<RadioOptionGroup
name={`pregunta_${pregunta.pregunta.id_pregunta}`}
options={opciones}
selectedValue={valor ? Number(valor) : undefined}
onChange={(opt) =>
setRespuesta(pregunta.pregunta.id_pregunta.toString(), String(opt.value))
}
/>
</div>
);
}
return null;
})}
</div>
))}
{/* Botón de envío */}
{mostrarFormularioCompleto && (
<Button
className="btn btn-primary"
onClick={handleOnSubmit}
disabled={isSending}
>
{isSending ? 'Enviando...' : 'Enviar Formulario'}
</Button>
)}
</div>
);
}