d3e1441f87
- 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.
248 lines
8.4 KiB
TypeScript
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>
|
|
);
|
|
}
|