auto complete alumnos
This commit is contained in:
@@ -1,19 +1,23 @@
|
||||
'use client';
|
||||
import Link from 'next/link';
|
||||
import { useParams } from 'next/navigation';
|
||||
import React from 'react';
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
const params = useParams<{ id_formulario: string }>();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<nav className='d-flex gap-2 my-4'>
|
||||
<Link href={'/administrador/'} className='text-decoration-none'>
|
||||
<Link href={`/administrador/formulario/${params.id_formulario}`} className='text-decoration-none'>
|
||||
<div className='box'>Registros</div>
|
||||
</Link>
|
||||
<Link
|
||||
{/* <Link
|
||||
href={'/administrador/crear-formulario'}
|
||||
className='text-decoration-none'
|
||||
>
|
||||
<div className='box'>Editar formulario</div>
|
||||
</Link>
|
||||
</Link> */}
|
||||
</nav>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
+24
-13
@@ -36,6 +36,17 @@ export default async function Page() {
|
||||
return <div>Error al cargar los eventos</div>;
|
||||
}
|
||||
|
||||
function slugify(text: string): string {
|
||||
return text
|
||||
.toString()
|
||||
.normalize('NFD') // Quita tildes
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/[^a-z0-9]+/g, '-') // Reemplaza cualquier cosa que no sea alfanumérica por guiones
|
||||
.replace(/^-+|-+$/g, ''); // Quita guiones al inicio o final
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mx-auto my-5">
|
||||
@@ -44,19 +55,19 @@ export default async function Page() {
|
||||
<div className="cards-1 mt-5">
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
{eventos.map((cuestionario) => (
|
||||
<FormularioCard
|
||||
key={cuestionario.id_cuestionario}
|
||||
cuestionario={cuestionario}
|
||||
link={`/registro/${cuestionario.id_cuestionario}`}
|
||||
button_message='Registrarse'
|
||||
/>
|
||||
))}
|
||||
{eventos.length === 0 && (
|
||||
<p className="text-center text-muted">
|
||||
No hay formularios disponibles.
|
||||
</p>
|
||||
)}
|
||||
{eventos.map((cuestionario) => {
|
||||
const slug = slugify(cuestionario.nombre_form); // Asumiendo que el nombre del evento está en `nombre`
|
||||
const url = `/registro/${slug}-${cuestionario.id_cuestionario}`;
|
||||
|
||||
return (
|
||||
<FormularioCard
|
||||
key={cuestionario.id_cuestionario}
|
||||
cuestionario={cuestionario}
|
||||
link={url}
|
||||
button_message="Registrarse"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,306 +0,0 @@
|
||||
'use client';
|
||||
import Button from '@/components/button';
|
||||
import CheckboxOptionGroup from '@/components/checkbox-option-group';
|
||||
import Input from '@/components/input';
|
||||
import RadioOptionGroup from '@/components/radio-option-group';
|
||||
import { CuestionarioResponse } from '@/types/responder-formulario';
|
||||
import axiosInstance from '@/utils/api-config';
|
||||
import { validarRespuesta } from '@/utils/validador';
|
||||
import Image from 'next/image';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
export default function Page() {
|
||||
const router = useRouter();
|
||||
const params = useParams<{ id_formulario: string }>();
|
||||
const id_formulario = params?.id_formulario;
|
||||
const [cuestionario, setCuestionario] = useState<CuestionarioResponse | null>(
|
||||
null
|
||||
);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [respuestas, setRespuestas] = useState<
|
||||
Record<number, string | number | Array<string | number>>
|
||||
>({});
|
||||
const [isSubmitting, setIsSubmitting] = useState(false); // New state for submission
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const { data } = await axiosInstance.get<CuestionarioResponse>(
|
||||
`/cuestionario/${id_formulario}/formulario`
|
||||
);
|
||||
if (data) {
|
||||
console.log(data);
|
||||
setCuestionario(data);
|
||||
} else {
|
||||
setError('Error fetching data');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching data:', error);
|
||||
setError('Error de conexión');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (id_formulario) fetchData();
|
||||
}, [id_formulario]);
|
||||
|
||||
const handleInputChange = (id: number, value: string) => {
|
||||
setRespuestas((prev) => ({ ...prev, [id]: value }));
|
||||
};
|
||||
|
||||
const handleRadioChange = (id: number, opcion: { value: number }) => {
|
||||
setRespuestas((prev) => ({ ...prev, [id]: opcion.value }));
|
||||
};
|
||||
|
||||
const handleCheckboxChange = (id: number, opcion: { value: number }) => {
|
||||
setRespuestas((prev) => {
|
||||
const actuales: number[] = Array.isArray(prev[id])
|
||||
? (prev[id] as number[])
|
||||
: [];
|
||||
const existe = actuales.includes(opcion.value);
|
||||
const nuevos = existe
|
||||
? actuales.filter((v: number) => v !== opcion.value)
|
||||
: [...actuales, opcion.value];
|
||||
return { ...prev, [id]: nuevos };
|
||||
});
|
||||
};
|
||||
|
||||
const formatearRespuestas = async () => {
|
||||
if (!validarRespuestasObligatorias()) return;
|
||||
|
||||
const respuestasArray = Object.entries(respuestas).flatMap(
|
||||
([id, valor]) => {
|
||||
if (Array.isArray(valor)) {
|
||||
return valor.map((v) => ({
|
||||
id_pregunta: Number(id),
|
||||
valor: v,
|
||||
}));
|
||||
}
|
||||
return [{ id_pregunta: Number(id), valor }];
|
||||
}
|
||||
);
|
||||
|
||||
const correoEntry = respuestasArray.find(
|
||||
(r) =>
|
||||
typeof r.valor === 'string' &&
|
||||
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(r.valor)
|
||||
);
|
||||
|
||||
const resultado = {
|
||||
id_formulario: Number(id_formulario),
|
||||
correo: correoEntry?.valor || 'correo@no-encontrado.com',
|
||||
respuestas: respuestasArray,
|
||||
fecha_envio: new Date().toISOString(),
|
||||
};
|
||||
|
||||
setIsSubmitting(true); // Set submitting state to true
|
||||
toast
|
||||
.promise(
|
||||
axiosInstance.post('/cuestionario-respondido/submit', resultado),
|
||||
{
|
||||
loading: 'Enviando formulario...',
|
||||
success: 'Formulario enviado con éxito!',
|
||||
error: 'Error al enviar el formulario.',
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
router.push('/');
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error al enviar el formulario:', error);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsSubmitting(false); // Reset submitting state
|
||||
});
|
||||
};
|
||||
|
||||
const validarRespuestasObligatorias = (): boolean => {
|
||||
const faltantes: number[] = [];
|
||||
const errores: { id: number; mensaje: string }[] = [];
|
||||
|
||||
cuestionario?.cuestionario.secciones.forEach((seccion) => {
|
||||
seccion.preguntas.forEach(({ pregunta }) => {
|
||||
const id = pregunta.id_pregunta;
|
||||
const valor = respuestas[id];
|
||||
const tipo = pregunta.tipo_pregunta.tipo_pregunta;
|
||||
const validacion = pregunta.validacion;
|
||||
|
||||
const respondida =
|
||||
(tipo === 'Abierta' &&
|
||||
typeof valor === 'string' &&
|
||||
valor.trim() !== '') ||
|
||||
(tipo === 'Cerrada' && valor !== undefined) ||
|
||||
(tipo === 'Multiple' && Array.isArray(valor) && valor.length > 0);
|
||||
|
||||
if (!respondida) {
|
||||
if (pregunta.obligatoria) {
|
||||
faltantes.push(id);
|
||||
}
|
||||
return; // no hay nada más que validar si está vacía
|
||||
}
|
||||
|
||||
if (tipo === 'Abierta' && validacion) {
|
||||
const resultado = validarRespuesta(valor as string, validacion);
|
||||
if (!resultado.valido) {
|
||||
errores.push({ id, mensaje: resultado.mensaje });
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (faltantes.length > 0) {
|
||||
toast.error('Responde todas las preguntas obligatorias antes de enviar.');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (errores.length > 0) {
|
||||
toast.error(
|
||||
`Corrige las respuestas con error de formato:\n${errores
|
||||
.map((e) => `• ${e.mensaje}`)
|
||||
.join('\n')}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
if (loading) return <p>Cargando...</p>;
|
||||
if (error) return <p className="text-danger">{error}</p>;
|
||||
if (!cuestionario) return <p>No hay datos</p>;
|
||||
|
||||
return (
|
||||
<div className="container py-4">
|
||||
<Button icon="arrow-left" onClick={() => router.back()}>
|
||||
Volver
|
||||
</Button>
|
||||
<div className="text-center mb-4">
|
||||
<Image
|
||||
src={'/banner1.png'}
|
||||
width={1000}
|
||||
height={300}
|
||||
alt="Ejemplo de banner"
|
||||
className="rounded-4 shadow-sm img-fluid"
|
||||
style={{
|
||||
objectFit: 'cover',
|
||||
objectPosition: 'center',
|
||||
height: 300,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="px-lg-5 mx-lg-5">
|
||||
<h2>{cuestionario.cuestionario.nombre_form}</h2>
|
||||
<p>{cuestionario.cuestionario.descripcion}</p>
|
||||
|
||||
{cuestionario.cuestionario.secciones.map((seccion, index) => (
|
||||
<section key={index} className="mb-4">
|
||||
<h4>{seccion.seccion.titulo}</h4>
|
||||
<p>{seccion.seccion.descripcion}</p>
|
||||
|
||||
{seccion.preguntas.map((p) => {
|
||||
const pregunta = p.pregunta;
|
||||
const id = pregunta.id_pregunta;
|
||||
const tipo = pregunta.tipo_pregunta.tipo_pregunta;
|
||||
|
||||
if (tipo === 'Abierta') {
|
||||
return (
|
||||
<Input
|
||||
key={id}
|
||||
label={pregunta.pregunta}
|
||||
name={`pregunta_${id}`}
|
||||
value={
|
||||
Array.isArray(respuestas[id])
|
||||
? respuestas[id].join(', ')
|
||||
: respuestas[id] || ''
|
||||
}
|
||||
maxLength={250} // Aplica límite si existe
|
||||
onChange={(e) => handleInputChange(id, e.target.value)}
|
||||
required={pregunta.obligatoria}
|
||||
placeholder={
|
||||
pregunta.validacion === 'correo'
|
||||
? 'ejemplo@correo.com'
|
||||
: pregunta.validacion === 'cuenta_alumno'
|
||||
? '123456789'
|
||||
: undefined
|
||||
}
|
||||
type={
|
||||
pregunta.validacion === 'correo'
|
||||
? 'email'
|
||||
: pregunta.validacion === 'cuenta_alumno'
|
||||
? 'number'
|
||||
: 'text'
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (tipo === 'Cerrada' || tipo === 'Multiple') {
|
||||
const opciones = (pregunta.opciones ?? []).map((o) => ({
|
||||
label: o.opcion.opcion,
|
||||
value: o.opcion.id_opcion,
|
||||
}));
|
||||
|
||||
if (tipo === 'Multiple') {
|
||||
return (
|
||||
<div key={id} className="mb-3">
|
||||
<p>{pregunta.pregunta}</p>
|
||||
<CheckboxOptionGroup
|
||||
name={`pregunta_${id}`}
|
||||
options={opciones}
|
||||
selectedValues={
|
||||
Array.isArray(respuestas[id]) ? respuestas[id] : []
|
||||
}
|
||||
onChange={(opcion) => {
|
||||
if (typeof opcion.value === 'number') {
|
||||
handleCheckboxChange(id, { value: opcion.value });
|
||||
} else {
|
||||
console.error('Invalid value type:', opcion.value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={id} className="mb-3">
|
||||
<p>{pregunta.pregunta}</p>
|
||||
<RadioOptionGroup
|
||||
name={`pregunta_${id}`}
|
||||
options={opciones}
|
||||
selectedValue={respuestas[id]}
|
||||
onChange={(opcion) => {
|
||||
if (typeof opcion.value === 'number') {
|
||||
handleRadioChange(id, { value: opcion.value });
|
||||
} else {
|
||||
console.error('Invalid value type:', opcion.value);
|
||||
}
|
||||
}}
|
||||
disabled={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
})}
|
||||
</section>
|
||||
))}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary"
|
||||
onClick={formatearRespuestas}
|
||||
disabled={isSubmitting} // Disable button while submitting
|
||||
>
|
||||
{isSubmitting ? 'Enviando...' : 'Enviar'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
'use client';
|
||||
import Button from '@/components/button';
|
||||
import Formulario from '@/containers/formulario';
|
||||
import { CuestionarioResponse } from '@/types/responder-formulario';
|
||||
import axiosInstance from '@/utils/api-config';
|
||||
import Image from 'next/image';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
export default function Page() {
|
||||
const router = useRouter();
|
||||
const params = useParams<{ slug: string }>();
|
||||
const slug = params?.slug;
|
||||
|
||||
const id_formulario = slug?.split('-').pop(); // Toma el último fragmento como ID
|
||||
const [cuestionario, setCuestionario] = useState<CuestionarioResponse | null>(
|
||||
null
|
||||
);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const { data } = await axiosInstance.get<CuestionarioResponse>(
|
||||
`/cuestionario/${id_formulario}/formulario`
|
||||
);
|
||||
if (data) {
|
||||
console.log(data);
|
||||
setCuestionario(data);
|
||||
} else {
|
||||
setError('Error fetching data');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching data:', error);
|
||||
setError('Error de conexión');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (id_formulario) fetchData();
|
||||
}, [id_formulario]);
|
||||
|
||||
if (loading) return <p>Cargando...</p>;
|
||||
if (error) return <p className="text-danger">{error}</p>;
|
||||
if (!cuestionario) return <p>No hay datos</p>;
|
||||
|
||||
return (
|
||||
<div className="container py-4">
|
||||
<Button icon="arrow-left" onClick={() => router.back()}>
|
||||
Volver
|
||||
</Button>
|
||||
<div className="text-center mb-4">
|
||||
<Image
|
||||
src={'/banner1.png'}
|
||||
width={1000}
|
||||
height={300}
|
||||
alt="Ejemplo de banner"
|
||||
className="rounded-4 shadow-sm img-fluid"
|
||||
style={{
|
||||
objectFit: 'cover',
|
||||
objectPosition: 'center',
|
||||
height: 300,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="px-lg-5 mx-lg-5">
|
||||
<h2>{cuestionario.cuestionario.nombre_form}</h2>
|
||||
<p>{cuestionario.cuestionario.descripcion}</p>
|
||||
|
||||
<Formulario
|
||||
id_formulario={cuestionario.cuestionario.id_cuestionario}
|
||||
secciones={cuestionario.cuestionario.secciones}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
'use client';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { CuestionarioSeccion } from '@/types/responder-formulario';
|
||||
import RadioOptionGroup, { RadioOption } from '@/components/radio-option-group';
|
||||
import Input from '@/components/input';
|
||||
import Button from '@/components/button';
|
||||
import { validarRespuesta } from '@/utils/validador';
|
||||
import toast from 'react-hot-toast';
|
||||
import axiosInstance from '@/utils/api-config';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
export default function Formulario({
|
||||
id_formulario,
|
||||
secciones,
|
||||
}: {
|
||||
id_formulario: number;
|
||||
secciones: CuestionarioSeccion[];
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const [esDeFES, setEsDeFES] = useState<boolean | null>(null);
|
||||
const [cuenta, setCuenta] = useState('');
|
||||
const [datosAuto, setDatosAuto] = useState<null | {
|
||||
nombre: string;
|
||||
apellidos: string;
|
||||
correo: string;
|
||||
}>(null);
|
||||
const [respuestas, setRespuestas] = useState<Record<string, string>>({});
|
||||
const [isSubmitting, setIsSubmitting] = useState(false); // New state for submission
|
||||
|
||||
const actualizarRespuesta = (idPregunta: string, valor: string) => {
|
||||
setRespuestas((prev) => ({ ...prev, [idPregunta]: valor }));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (esDeFES && cuenta.length === 9) {
|
||||
fetch(`/datos/alumnos/${cuenta}`)
|
||||
.then((res) => res.json())
|
||||
.then((data) => setDatosAuto(data))
|
||||
.catch((err) => {
|
||||
console.error('Error al obtener datos del alumno:', err);
|
||||
setDatosAuto(null);
|
||||
});
|
||||
}
|
||||
}, [cuenta, esDeFES]);
|
||||
|
||||
// Encuentra preguntas por texto o validación
|
||||
const todasPreguntas = secciones.flatMap((s) =>
|
||||
s.preguntas.map((p) => p.pregunta)
|
||||
);
|
||||
const preguntaFES = todasPreguntas.find(
|
||||
(p) => p.pregunta === '¿Eres parte de la comunidad de la FES Acatlán?'
|
||||
);
|
||||
const preguntaCuenta = todasPreguntas.find(
|
||||
(p) => p.validacion === 'cuenta_alumno'
|
||||
);
|
||||
const preguntasRestantes = todasPreguntas.filter(
|
||||
(p) =>
|
||||
![preguntaFES?.id_pregunta, preguntaCuenta?.id_pregunta].includes(
|
||||
p.id_pregunta
|
||||
)
|
||||
);
|
||||
const respuestaFES = respuestas[`pregunta_${preguntaFES?.id_pregunta}`];
|
||||
|
||||
const formatearRespuestas = async () => {
|
||||
if (!validarRespuestasObligatorias()) return;
|
||||
|
||||
const respuestasArray = Object.entries(respuestas).flatMap(
|
||||
([key, valor]) => {
|
||||
const idNumerico = Number(key.replace('pregunta_', ''));
|
||||
const pregunta = secciones
|
||||
.flatMap((s) => s.preguntas)
|
||||
.find((p) => p.pregunta.id_pregunta === idNumerico);
|
||||
|
||||
const tipo = pregunta?.pregunta.tipo_pregunta.tipo_pregunta;
|
||||
|
||||
// Si es múltiple, convertir cada valor individual si corresponde
|
||||
if (Array.isArray(valor)) {
|
||||
return valor.map((v) => ({
|
||||
id_pregunta: idNumerico,
|
||||
valor: tipo === 'Cerrada' || tipo === 'Multiple' ? Number(v) : v,
|
||||
}));
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
id_pregunta: idNumerico,
|
||||
valor: tipo === 'Cerrada' ? Number(valor) : valor,
|
||||
},
|
||||
];
|
||||
}
|
||||
);
|
||||
|
||||
const correoEntry = respuestasArray.find(
|
||||
(r) =>
|
||||
typeof r.valor === 'string' &&
|
||||
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(r.valor)
|
||||
);
|
||||
|
||||
const resultado = {
|
||||
id_formulario: Number(id_formulario),
|
||||
correo: correoEntry?.valor || 'correo@no-encontrado.com',
|
||||
respuestas: respuestasArray,
|
||||
fecha_envio: new Date().toISOString(),
|
||||
};
|
||||
|
||||
console.log('Resultado a enviar:', resultado);
|
||||
|
||||
setIsSubmitting(true);
|
||||
toast
|
||||
.promise(
|
||||
axiosInstance.post('/cuestionario-respondido/submit', resultado),
|
||||
{
|
||||
loading: 'Enviando formulario...',
|
||||
success: 'Formulario enviado con éxito!',
|
||||
error: 'Error al enviar el formulario.',
|
||||
}
|
||||
)
|
||||
.then(() => router.push('/'))
|
||||
.finally(() => setIsSubmitting(false));
|
||||
};
|
||||
|
||||
const validarRespuestasObligatorias = (): boolean => {
|
||||
const faltantes: number[] = [];
|
||||
const errores: { id: number; mensaje: string }[] = [];
|
||||
console.log('Validando respuestas:', respuestas);
|
||||
|
||||
secciones.forEach((seccion) => {
|
||||
seccion.preguntas.forEach(({ pregunta }) => {
|
||||
const id = pregunta.id_pregunta;
|
||||
const valor = respuestas[`pregunta_${id}`];
|
||||
const tipo = pregunta.tipo_pregunta.tipo_pregunta;
|
||||
const validacion = pregunta.validacion;
|
||||
|
||||
const respondida =
|
||||
(tipo === 'Abierta' &&
|
||||
typeof valor === 'string' &&
|
||||
valor.trim() !== '') ||
|
||||
(tipo === 'Cerrada' && valor !== undefined) ||
|
||||
(tipo === 'Multiple' && Array.isArray(valor) && valor.length > 0);
|
||||
|
||||
if (!respondida && pregunta.obligatoria) {
|
||||
faltantes.push(id);
|
||||
}
|
||||
|
||||
if (tipo === 'Abierta' && validacion && typeof valor === 'string') {
|
||||
const resultado = validarRespuesta(valor, validacion);
|
||||
if (!resultado.valido) {
|
||||
errores.push({ id, mensaje: resultado.mensaje });
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
if (faltantes.length > 0) {
|
||||
console.log('Faltan preguntas obligatorias:', faltantes);
|
||||
toast.error('Responde todas las preguntas obligatorias antes de enviar.');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (errores.length > 0) {
|
||||
toast.error(
|
||||
`Corrige las respuestas con error de formato:\n${errores
|
||||
.map((e) => `• ${e.mensaje}`)
|
||||
.join('\n')}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
return (
|
||||
<form className="container py-4">
|
||||
{/* Pregunta inicial */}
|
||||
{preguntaFES && (
|
||||
<div className="mb-4">
|
||||
<label className="form-label">{preguntaFES.pregunta}</label>
|
||||
<RadioOptionGroup
|
||||
name="comunidad_fes"
|
||||
options={preguntaFES.opciones.map((op) => ({
|
||||
label: op.opcion.opcion,
|
||||
value: op.id_opcion,
|
||||
}))}
|
||||
selectedValue={respuestaFES ? Number(respuestaFES) : undefined}
|
||||
onChange={(opt) => {
|
||||
const idSeleccionado = opt.value;
|
||||
const opcionSeleccionada = preguntaFES.opciones.find(
|
||||
(o) => o.id_opcion === idSeleccionado
|
||||
);
|
||||
|
||||
const valorTexto = opcionSeleccionada?.opcion.opcion ?? '';
|
||||
setEsDeFES(valorTexto === 'Si'); // Se actualiza esDeFES con el texto
|
||||
setCuenta('');
|
||||
setDatosAuto(null);
|
||||
|
||||
actualizarRespuesta(
|
||||
`pregunta_${preguntaFES.id_pregunta}`,
|
||||
String(idSeleccionado)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Si dijo que sí, pedir número de cuenta */}
|
||||
{esDeFES && preguntaCuenta && (
|
||||
<Input
|
||||
key={preguntaCuenta.id_pregunta}
|
||||
label={preguntaCuenta.pregunta}
|
||||
name={`pregunta_${preguntaCuenta.id_pregunta}`}
|
||||
value={cuenta}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
setCuenta(value);
|
||||
actualizarRespuesta(
|
||||
`pregunta_${preguntaCuenta.id_pregunta}`,
|
||||
value
|
||||
); // ✅ Guarda la respuesta
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Si ya tiene datos o dijo que no es de la FES, mostrar el resto */}
|
||||
{(esDeFES === false || (esDeFES && datosAuto)) &&
|
||||
preguntasRestantes.map((p) => {
|
||||
if (p.tipo_pregunta.tipo_pregunta === 'Abierta') {
|
||||
let defaultValue = '';
|
||||
let disabled = false;
|
||||
|
||||
if (esDeFES && datosAuto) {
|
||||
if (p.validacion === 'nombre') {
|
||||
defaultValue = p.pregunta.toLowerCase().includes('apellido')
|
||||
? datosAuto.apellidos
|
||||
: datosAuto.nombre;
|
||||
disabled = true;
|
||||
}
|
||||
if (p.validacion === 'correo') {
|
||||
defaultValue = datosAuto.correo;
|
||||
disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Input
|
||||
key={p.id_pregunta}
|
||||
label={p.pregunta}
|
||||
name={`pregunta_${p.id_pregunta}`}
|
||||
defaultValue={defaultValue}
|
||||
disabled={disabled}
|
||||
onChange={(e) =>
|
||||
actualizarRespuesta(
|
||||
`pregunta_${p.id_pregunta}`,
|
||||
e.target.value
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (p.tipo_pregunta.tipo_pregunta === 'Cerrada') {
|
||||
const opciones: RadioOption<number>[] = p.opciones.map(
|
||||
(op) => ({
|
||||
label: op.opcion.opcion,
|
||||
value: op.id_opcion,
|
||||
})
|
||||
);
|
||||
|
||||
const respuestaActual = respuestas[`pregunta_${p.id_pregunta}`];
|
||||
|
||||
return (
|
||||
<div key={p.id_pregunta}>
|
||||
<label className="form-label">{p.pregunta}</label>
|
||||
<RadioOptionGroup
|
||||
name={`pregunta_${p.id_pregunta}`}
|
||||
options={opciones}
|
||||
selectedValue={
|
||||
respuestaActual ? Number(respuestaActual) : undefined
|
||||
} // ✅ Aquí se fija la opción
|
||||
onChange={(opt) =>
|
||||
actualizarRespuesta(
|
||||
`pregunta_${p.id_pregunta}`,
|
||||
String(opt.value)
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
})}
|
||||
|
||||
{/* Botón de enviar */}
|
||||
<Button onClick={formatearRespuestas} disabled={isSubmitting}>
|
||||
{isSubmitting ? 'Enviando...' : 'Enviar'}
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -42,7 +42,7 @@ export const plantillasDisponibles: Plantilla[] = [
|
||||
{
|
||||
titulo: 'Numero de cuenta',
|
||||
tipo: 'Abierta',
|
||||
obligatoria: true,
|
||||
obligatoria: false,
|
||||
limite: 250,
|
||||
validacion: 'cuenta_alumno',
|
||||
},
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { CuestionarioResponse } from "@/types/responder-formulario";
|
||||
import toast from "react-hot-toast";
|
||||
import { validarRespuesta } from "./validador";
|
||||
|
||||
export const validarRespuestasObligatorias = (
|
||||
cuestionario: CuestionarioResponse | null,
|
||||
respuestas: Record<number, string | number | Array<string | number>>
|
||||
): boolean => {
|
||||
const faltantes: number[] = [];
|
||||
const errores: { id: number; mensaje: string }[] = [];
|
||||
|
||||
cuestionario?.cuestionario.secciones.forEach((seccion) => {
|
||||
seccion.preguntas.forEach(({ pregunta }) => {
|
||||
const id = pregunta.id_pregunta;
|
||||
const valor = respuestas[id];
|
||||
const tipo = pregunta.tipo_pregunta.tipo_pregunta;
|
||||
const validacion = pregunta.validacion;
|
||||
|
||||
const respondida =
|
||||
(tipo === 'Abierta' &&
|
||||
typeof valor === 'string' &&
|
||||
valor.trim() !== '') ||
|
||||
(tipo === 'Cerrada' && valor !== undefined) ||
|
||||
(tipo === 'Multiple' && Array.isArray(valor) && valor.length > 0);
|
||||
|
||||
if (!respondida) {
|
||||
if (pregunta.obligatoria) {
|
||||
faltantes.push(id);
|
||||
}
|
||||
return; // no hay nada más que validar si está vacía
|
||||
}
|
||||
|
||||
if (tipo === 'Abierta' && validacion) {
|
||||
const resultado = validarRespuesta(valor as string, validacion);
|
||||
if (!resultado.valido) {
|
||||
errores.push({ id, mensaje: resultado.mensaje });
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (faltantes.length > 0) {
|
||||
toast.error('Responde todas las preguntas obligatorias antes de enviar.');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (errores.length > 0) {
|
||||
toast.error(
|
||||
`Corrige las respuestas con error de formato:\n${errores
|
||||
.map((e) => `• ${e.mensaje}`)
|
||||
.join('\n')}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
Reference in New Issue
Block a user