Responder y crear formularios
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 2.4 MiB |
@@ -0,0 +1,489 @@
|
||||
'use client';
|
||||
import React, { useState } from 'react';
|
||||
import Pagination from '@/components/pagination';
|
||||
import Image from 'next/image';
|
||||
|
||||
interface Opcion {
|
||||
valor: string;
|
||||
}
|
||||
|
||||
interface Pregunta {
|
||||
titulo: string;
|
||||
tipo: 'Abierta' | 'Cerrada' | 'Multiple';
|
||||
opciones: Opcion[];
|
||||
obligatoria: boolean;
|
||||
}
|
||||
|
||||
interface Seccion {
|
||||
titulo: string;
|
||||
descripcion: string;
|
||||
preguntas: Pregunta[];
|
||||
}
|
||||
|
||||
export default function AdminFormPage() {
|
||||
const [nombreFormulario, setNombreFormulario] = useState('');
|
||||
const [descripcion, setDescripcion] = useState('');
|
||||
const [secciones, setSecciones] = useState<Seccion[]>([]);
|
||||
const [paginaActual, setPaginaActual] = useState(1);
|
||||
const [bannerPreview, setBannerPreview] = useState<string | null>(null);
|
||||
const [audiencia, setAudiencia] = useState<'interna' | 'externa' | 'ambas'>(
|
||||
'interna'
|
||||
);
|
||||
|
||||
const swapItems = <T,>(arr: T[], index1: number, index2: number): T[] => {
|
||||
const result = [...arr];
|
||||
[result[index1], result[index2]] = [result[index2], result[index1]];
|
||||
return result;
|
||||
};
|
||||
|
||||
const moverSeccion = (from: number, to: number) => {
|
||||
if (to >= 0 && to < secciones.length) {
|
||||
setSecciones((prev) => swapItems(prev, from, to));
|
||||
setPaginaActual(to + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const moverPregunta = (seccionIndex: number, from: number, to: number) => {
|
||||
const nuevas = [...secciones];
|
||||
const preguntas = nuevas[seccionIndex].preguntas;
|
||||
if (to >= 0 && to < preguntas.length) {
|
||||
nuevas[seccionIndex].preguntas = swapItems(preguntas, from, to);
|
||||
setSecciones(nuevas);
|
||||
}
|
||||
};
|
||||
|
||||
const agregarSeccion = () => {
|
||||
setSecciones((prev) => [
|
||||
...prev,
|
||||
{ titulo: '', descripcion: '', preguntas: [] },
|
||||
]);
|
||||
setPaginaActual(secciones.length + 1);
|
||||
};
|
||||
|
||||
const eliminarSeccion = (index: number) => {
|
||||
const nuevas = secciones.filter((_, i) => i !== index);
|
||||
setSecciones(nuevas);
|
||||
setPaginaActual((prev) =>
|
||||
Math.max(1, prev > nuevas.length ? nuevas.length : prev)
|
||||
);
|
||||
};
|
||||
|
||||
const actualizarSeccion = <K extends keyof Seccion>(
|
||||
index: number,
|
||||
campo: K,
|
||||
valor: Seccion[K]
|
||||
) => {
|
||||
setSecciones((prev) => {
|
||||
const nuevas = [...prev];
|
||||
nuevas[index][campo] = valor;
|
||||
return nuevas;
|
||||
});
|
||||
};
|
||||
|
||||
const agregarPregunta = (seccionIndex: number) => {
|
||||
const nuevas = [...secciones];
|
||||
nuevas[seccionIndex].preguntas.push({
|
||||
titulo: '',
|
||||
tipo: 'Abierta',
|
||||
opciones: [],
|
||||
obligatoria: false,
|
||||
});
|
||||
setSecciones(nuevas);
|
||||
};
|
||||
|
||||
const eliminarPregunta = (seccionIndex: number, preguntaIndex: number) => {
|
||||
const nuevas = [...secciones];
|
||||
nuevas[seccionIndex].preguntas.splice(preguntaIndex, 1);
|
||||
setSecciones(nuevas);
|
||||
};
|
||||
|
||||
const actualizarPregunta = (
|
||||
seccionIndex: number,
|
||||
preguntaIndex: number,
|
||||
campo: keyof Pregunta,
|
||||
valor: Pregunta[keyof Pregunta]
|
||||
) => {
|
||||
setSecciones((prev) => {
|
||||
const nuevas = [...prev];
|
||||
const pregunta = nuevas[seccionIndex].preguntas[preguntaIndex];
|
||||
nuevas[seccionIndex].preguntas[preguntaIndex] = {
|
||||
...pregunta,
|
||||
[campo]: valor,
|
||||
};
|
||||
return nuevas;
|
||||
});
|
||||
};
|
||||
const agregarOpcion = (seccionIndex: number, preguntaIndex: number) => {
|
||||
const nuevas = [...secciones];
|
||||
nuevas[seccionIndex].preguntas[preguntaIndex].opciones.push({ valor: '' });
|
||||
setSecciones(nuevas);
|
||||
};
|
||||
|
||||
const eliminarOpcion = (
|
||||
seccionIndex: number,
|
||||
preguntaIndex: number,
|
||||
opcionIndex: number
|
||||
) => {
|
||||
const nuevas = [...secciones];
|
||||
nuevas[seccionIndex].preguntas[preguntaIndex].opciones.splice(
|
||||
opcionIndex,
|
||||
1
|
||||
);
|
||||
setSecciones(nuevas);
|
||||
};
|
||||
|
||||
const actualizarOpcion = (
|
||||
seccionIndex: number,
|
||||
preguntaIndex: number,
|
||||
opcionIndex: number,
|
||||
valor: string
|
||||
) => {
|
||||
const nuevas = [...secciones];
|
||||
nuevas[seccionIndex].preguntas[preguntaIndex].opciones[opcionIndex].valor =
|
||||
valor;
|
||||
setSecciones(nuevas);
|
||||
};
|
||||
|
||||
const guardarFormulario = () => {
|
||||
const formData = {
|
||||
nombre: nombreFormulario,
|
||||
descripcion,
|
||||
secciones,
|
||||
};
|
||||
console.log('Formulario guardado:', JSON.stringify(formData));
|
||||
alert('Formulario guardado en consola');
|
||||
};
|
||||
|
||||
const seccionActual = secciones[paginaActual - 1];
|
||||
|
||||
const handleBannerChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0] || null;
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => setBannerPreview(reader.result as string);
|
||||
reader.readAsDataURL(file);
|
||||
} else {
|
||||
setBannerPreview(null);
|
||||
}
|
||||
};
|
||||
|
||||
const imprimirFormulario = () => {
|
||||
const data = {
|
||||
banner: bannerPreview,
|
||||
nombreFormulario,
|
||||
descripcion,
|
||||
secciones,
|
||||
};
|
||||
console.log('Formulario a imprimir:', JSON.stringify(data));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2 className='my-4'>Crear nuevo formulario</h2>
|
||||
|
||||
<div className='mb-4'>
|
||||
<label className='form-label'>Banner del formulario</label>
|
||||
<input
|
||||
type='file'
|
||||
accept='image/*'
|
||||
className='form-control mb-2'
|
||||
onChange={handleBannerChange}
|
||||
/>
|
||||
{bannerPreview && (
|
||||
<div className='text-center'>
|
||||
<Image
|
||||
src={bannerPreview}
|
||||
width={1000}
|
||||
height={300}
|
||||
alt='Ejemplo de banner'
|
||||
className='rounded-4 shadow-sm'
|
||||
style={{
|
||||
objectFit: 'cover',
|
||||
objectPosition: 'center',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='mb-3'>
|
||||
<label className='form-label'>Nombre del formulario</label>
|
||||
<input
|
||||
type='text'
|
||||
className='form-control'
|
||||
value={nombreFormulario}
|
||||
onChange={(e) => setNombreFormulario(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mb-3'>
|
||||
<label className='form-label'>Descripción</label>
|
||||
<textarea
|
||||
className='form-control'
|
||||
rows={3}
|
||||
value={descripcion}
|
||||
onChange={(e) => setDescripcion(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className='mb-4'>
|
||||
<label className='form-label d-block'>Audiencia del formulario</label>
|
||||
<div className='form-check form-check-inline'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='radio'
|
||||
name='audiencia'
|
||||
id='audienciaInterna'
|
||||
value='interna'
|
||||
checked={audiencia === 'interna'}
|
||||
onChange={() => setAudiencia('interna')}
|
||||
/>
|
||||
<label className='form-check-label' htmlFor='audienciaInterna'>
|
||||
Interna (facultad)
|
||||
</label>
|
||||
</div>
|
||||
<div className='form-check form-check-inline'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='radio'
|
||||
name='audiencia'
|
||||
id='audienciaExterna'
|
||||
value='externa'
|
||||
checked={audiencia === 'externa'}
|
||||
onChange={() => setAudiencia('externa')}
|
||||
/>
|
||||
<label className='form-check-label' htmlFor='audienciaExterna'>
|
||||
Externa
|
||||
</label>
|
||||
</div>
|
||||
<div className='form-check form-check-inline'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='radio'
|
||||
name='audiencia'
|
||||
id='audienciaAmbas'
|
||||
value='ambas'
|
||||
checked={audiencia === 'ambas'}
|
||||
onChange={() => setAudiencia('ambas')}
|
||||
/>
|
||||
<label className='form-check-label' htmlFor='audienciaAmbas'>
|
||||
Ambas
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mb-4'>
|
||||
<button
|
||||
className='btn btn-outline-primary mb-3'
|
||||
onClick={agregarSeccion}
|
||||
>
|
||||
+ Agregar sección
|
||||
</button>
|
||||
|
||||
{seccionActual && (
|
||||
<div className='border rounded p-3 mb-4'>
|
||||
<div className='d-flex justify-content-between align-items-center'>
|
||||
<h5>Sección {paginaActual}</h5>
|
||||
<div className='d-flex gap-2'>
|
||||
<button
|
||||
className='btn btn-sm btn-outline-secondary'
|
||||
onClick={() =>
|
||||
moverSeccion(paginaActual - 1, paginaActual - 2)
|
||||
}
|
||||
disabled={paginaActual === 1}
|
||||
>
|
||||
↑
|
||||
</button>
|
||||
<button
|
||||
className='btn btn-sm btn-outline-secondary'
|
||||
onClick={() => moverSeccion(paginaActual - 1, paginaActual)}
|
||||
disabled={paginaActual === secciones.length}
|
||||
>
|
||||
↓
|
||||
</button>
|
||||
<button
|
||||
className='btn btn-sm btn-danger'
|
||||
onClick={() => eliminarSeccion(paginaActual - 1)}
|
||||
>
|
||||
Eliminar sección
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input
|
||||
type='text'
|
||||
className='form-control mb-2'
|
||||
placeholder='Título de la sección'
|
||||
value={seccionActual.titulo}
|
||||
onChange={(e) =>
|
||||
actualizarSeccion(paginaActual - 1, 'titulo', e.target.value)
|
||||
}
|
||||
/>
|
||||
<textarea
|
||||
className='form-control mb-3'
|
||||
placeholder='Descripción de la sección'
|
||||
value={seccionActual.descripcion}
|
||||
onChange={(e) =>
|
||||
actualizarSeccion(
|
||||
paginaActual - 1,
|
||||
'descripcion',
|
||||
e.target.value
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
{seccionActual.preguntas.map((pregunta, pIdx) => (
|
||||
<div key={pIdx} className='border rounded p-2 mb-3'>
|
||||
<div className='d-flex justify-content-between align-items-center'>
|
||||
<strong>Pregunta {pIdx + 1}</strong>
|
||||
<div className='d-flex gap-2'>
|
||||
<button
|
||||
className='btn btn-sm btn-outline-secondary'
|
||||
onClick={() =>
|
||||
moverPregunta(paginaActual - 1, pIdx, pIdx - 1)
|
||||
}
|
||||
disabled={pIdx === 0}
|
||||
>
|
||||
↑
|
||||
</button>
|
||||
<button
|
||||
className='btn btn-sm btn-outline-secondary'
|
||||
onClick={() =>
|
||||
moverPregunta(paginaActual - 1, pIdx, pIdx + 1)
|
||||
}
|
||||
disabled={pIdx === seccionActual.preguntas.length - 1}
|
||||
>
|
||||
↓
|
||||
</button>
|
||||
<button
|
||||
className='btn btn-sm btn-outline-danger'
|
||||
onClick={() => eliminarPregunta(paginaActual - 1, pIdx)}
|
||||
>
|
||||
Eliminar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type='text'
|
||||
className='form-control my-2'
|
||||
placeholder='Texto de la pregunta'
|
||||
value={pregunta.titulo}
|
||||
onChange={(e) =>
|
||||
actualizarPregunta(
|
||||
paginaActual - 1,
|
||||
pIdx,
|
||||
'titulo',
|
||||
e.target.value
|
||||
)
|
||||
}
|
||||
/>
|
||||
<select
|
||||
className='form-select mb-2'
|
||||
value={pregunta.tipo}
|
||||
onChange={(e) =>
|
||||
actualizarPregunta(
|
||||
paginaActual - 1,
|
||||
pIdx,
|
||||
'tipo',
|
||||
e.target.value as Pregunta['tipo']
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value='Abierta'>Abierta</option>
|
||||
<option value='Cerrada'>Cerrada</option>
|
||||
<option value='Multiple'>Opción múltiple</option>
|
||||
</select>
|
||||
|
||||
<div className='form-check form-switch mb-3'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
id={`obligatoria-${pIdx}`}
|
||||
checked={pregunta.obligatoria}
|
||||
onChange={(e) =>
|
||||
actualizarPregunta(
|
||||
paginaActual - 1,
|
||||
pIdx,
|
||||
'obligatoria',
|
||||
e.target.checked
|
||||
)
|
||||
}
|
||||
/>
|
||||
<label
|
||||
className='form-check-label'
|
||||
htmlFor={`obligatoria-${pIdx}`}
|
||||
>
|
||||
¿Es obligatoria?
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{(pregunta.tipo === 'Cerrada' ||
|
||||
pregunta.tipo === 'Multiple') && (
|
||||
<>
|
||||
<button
|
||||
className='btn btn-sm btn-outline-secondary mb-2'
|
||||
onClick={() => agregarOpcion(paginaActual - 1, pIdx)}
|
||||
>
|
||||
+ Agregar opción
|
||||
</button>
|
||||
{pregunta.opciones.map((op, oIdx) => (
|
||||
<div key={oIdx} className='input-group mb-2'>
|
||||
<input
|
||||
type='text'
|
||||
className='form-control'
|
||||
placeholder={`Opción ${oIdx + 1}`}
|
||||
value={op.valor}
|
||||
onChange={(e) =>
|
||||
actualizarOpcion(
|
||||
paginaActual - 1,
|
||||
pIdx,
|
||||
oIdx,
|
||||
e.target.value
|
||||
)
|
||||
}
|
||||
/>
|
||||
<button
|
||||
className='btn btn-outline-danger'
|
||||
onClick={() =>
|
||||
eliminarOpcion(paginaActual - 1, pIdx, oIdx)
|
||||
}
|
||||
>
|
||||
x
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
<button
|
||||
className='btn btn-sm btn-outline-primary'
|
||||
onClick={() => agregarPregunta(paginaActual - 1)}
|
||||
>
|
||||
+ Agregar pregunta
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{secciones.length > 1 && (
|
||||
<div className='d-flex justify-content-center mb-4'>
|
||||
<Pagination
|
||||
currentPage={paginaActual}
|
||||
totalPages={secciones.length}
|
||||
onPageChange={setPaginaActual}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button className='btn btn-success' onClick={guardarFormulario}>
|
||||
Guardar formulario
|
||||
</button>
|
||||
<div className='my-4 d-flex gap-2'>
|
||||
<button className='btn btn-success' onClick={imprimirFormulario}>
|
||||
Imprimir cuestionario
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import Footer from '@/components/layout/footer';
|
||||
import Header from '@/components/layout/header';
|
||||
import Link from 'next/link';
|
||||
import React from 'react';
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className='d-flex flex-column min-vh-100'>
|
||||
<Header />
|
||||
<main className='container flex-grow-1'>
|
||||
<nav className='d-flex gap-2 my-4'>
|
||||
<Link href={'/administrador/'} className='text-decoration-none'>
|
||||
<div className='box'>Formularios</div>
|
||||
</Link>
|
||||
<Link
|
||||
href={'/administrador/crear-formulario'}
|
||||
className='text-decoration-none'
|
||||
>
|
||||
<div className='box'>Crear cuestionario</div>
|
||||
</Link>
|
||||
</nav>
|
||||
{children}
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<h2 className='my-4'>Formularios</h2>
|
||||
</>
|
||||
);
|
||||
}
|
||||
+140
-5
@@ -1,12 +1,147 @@
|
||||
import Footer from "@/components/layout/footer";
|
||||
import Header from "@/components/layout/header";
|
||||
import React from "react";
|
||||
'use client';
|
||||
import Footer from '@/components/layout/footer';
|
||||
import Header from '@/components/layout/header';
|
||||
import React, { useState } from 'react';
|
||||
import { res } from '@/data/cuestionario';
|
||||
import RadioOptionGroup, { RadioOption } from '@/components/radio-option-group';
|
||||
import CheckboxOptionGroup, {
|
||||
CheckboxOption,
|
||||
} from '@/components/checkbox-option-group';
|
||||
import Pagination from '@/components/pagination';
|
||||
import Image from 'next/image';
|
||||
|
||||
type Respuesta = string | number | number[];
|
||||
|
||||
export default function Page() {
|
||||
const [respuestas, setRespuestas] = useState<Record<number, Respuesta>>({});
|
||||
const [paginaActual, setPaginaActual] = useState(1);
|
||||
const totalPaginas = res.cuestionario.secciones.length;
|
||||
|
||||
const handleRespuesta = (idPregunta: number, valor: string | number) => {
|
||||
setRespuestas((prev) => ({ ...prev, [idPregunta]: valor }));
|
||||
};
|
||||
|
||||
const handleMultipleRespuesta = (
|
||||
idPregunta: number,
|
||||
opcion: CheckboxOption<number>
|
||||
) => {
|
||||
setRespuestas((prev) => {
|
||||
const current = Array.isArray(prev[idPregunta])
|
||||
? (prev[idPregunta] as number[])
|
||||
: [];
|
||||
const updated = current.includes(opcion.value)
|
||||
? current.filter((val) => val !== opcion.value)
|
||||
: [...current, opcion.value];
|
||||
return { ...prev, [idPregunta]: updated };
|
||||
});
|
||||
};
|
||||
|
||||
const imprimirRespuestas = () => {
|
||||
console.log('Respuestas del cuestionario:', respuestas);
|
||||
alert('Respuestas impresas en consola');
|
||||
};
|
||||
|
||||
const seccion = res.cuestionario.secciones[paginaActual - 1];
|
||||
|
||||
return (
|
||||
<div className="d-flex flex-column min-vh-100">
|
||||
<div className='d-flex flex-column min-vh-100'>
|
||||
<Header />
|
||||
<div className="container flex-grow-1">
|
||||
<div className='container flex-grow-1'>
|
||||
<div className='text-center my-4'>
|
||||
<Image
|
||||
src={'/banner.png'}
|
||||
width={1000}
|
||||
height={300}
|
||||
alt='Ejemplo de banner'
|
||||
className='rounded-4 shadow-sm'
|
||||
style={{
|
||||
objectFit: 'cover',
|
||||
objectPosition: 'top',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<h1 className='mb-4'>{res.cuestionario.nombre_form}</h1>
|
||||
<p className='mb-5 text-muted'>{res.cuestionario.descripcion}</p>
|
||||
|
||||
<div className='mb-5 mx-md-5 px-md-5'>
|
||||
<h3>{seccion.seccion.titulo}</h3>
|
||||
<p className='text-muted'>{seccion.seccion.descripcion}</p>
|
||||
|
||||
{seccion.preguntas.map((preguntaWrap) => {
|
||||
const pregunta = preguntaWrap.pregunta;
|
||||
const id = pregunta.id_pregunta;
|
||||
|
||||
if (pregunta.tipo_pregunta.tipo_pregunta === 'Cerrada') {
|
||||
const opciones: RadioOption<number>[] = pregunta.opciones.map(
|
||||
(op) => ({
|
||||
label: op.opcion.opcion,
|
||||
value: op.opcion.id_opcion,
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<div key={id} className='mt-4'>
|
||||
<strong>{pregunta.pregunta}</strong>
|
||||
<RadioOptionGroup
|
||||
name={`pregunta-${id}`}
|
||||
options={opciones}
|
||||
selectedValue={respuestas[id] as number}
|
||||
onChange={(op) => handleRespuesta(id, op.value)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else if (pregunta.tipo_pregunta.tipo_pregunta === 'Abierta') {
|
||||
return (
|
||||
<div key={id} className='mt-4'>
|
||||
<strong>{pregunta.pregunta}</strong>
|
||||
<textarea
|
||||
className='form-control bg-white mt-2'
|
||||
rows={3}
|
||||
value={
|
||||
typeof respuestas[id] === 'string' ? respuestas[id] : ''
|
||||
}
|
||||
onChange={(e) => handleRespuesta(id, e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else if (pregunta.tipo_pregunta.tipo_pregunta === 'Multiple') {
|
||||
const opciones: CheckboxOption<number>[] = pregunta.opciones.map(
|
||||
(op) => ({
|
||||
label: op.opcion.opcion,
|
||||
value: op.opcion.id_opcion,
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<div key={id} className='mt-4'>
|
||||
<strong>{pregunta.pregunta}</strong>
|
||||
<CheckboxOptionGroup
|
||||
name={`pregunta-${id}`}
|
||||
options={opciones}
|
||||
selectedValues={(respuestas[id] as number[]) || []}
|
||||
onChange={(op) => handleMultipleRespuesta(id, op)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className='d-flex justify-content-center mb-4'>
|
||||
<Pagination
|
||||
currentPage={paginaActual}
|
||||
totalPages={totalPaginas}
|
||||
onPageChange={setPaginaActual}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='text-center mb-5'>
|
||||
<button className='btn btn-primary' onClick={imprimirRespuestas}>
|
||||
Imprimir respuestas en consola
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo } from "react";
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
interface PaginationProps {
|
||||
totalPages: number;
|
||||
@@ -43,15 +43,15 @@ export default function Pagination({
|
||||
}, [currentPage, totalPages, delta]);
|
||||
|
||||
return (
|
||||
<nav aria-label="Page navigation">
|
||||
<ul className="pagination gap-1">
|
||||
<nav aria-label='Page navigation'>
|
||||
<ul className='pagination gap-1'>
|
||||
{/* Botón Anterior */}
|
||||
<li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}>
|
||||
<li className={`page-item ${currentPage === 1 ? 'disabled' : ''}`}>
|
||||
<button
|
||||
className="page-link shadow-sm rounded border-0 bg-white"
|
||||
className='page-link box p-0'
|
||||
onClick={() => handlePageChange(currentPage - 1)}
|
||||
disabled={currentPage === 1}
|
||||
aria-label="Previous"
|
||||
aria-label='Previous'
|
||||
>
|
||||
«
|
||||
</button>
|
||||
@@ -60,21 +60,17 @@ export default function Pagination({
|
||||
{/* Números de Página */}
|
||||
{visiblePages.map((page, index) =>
|
||||
page === -1 ? (
|
||||
<li key={`ellipsis-${index}`} className="page-item disabled">
|
||||
<span className="page-link shadow-sm rounded border-0 bg-white">
|
||||
...
|
||||
</span>
|
||||
<li key={`ellipsis-${index}`} className='page-item disabled'>
|
||||
<span className='page-link box p-0'>...</span>
|
||||
</li>
|
||||
) : (
|
||||
<li
|
||||
key={page}
|
||||
className={`page-item ${currentPage === page ? "active" : ""}`}
|
||||
className={`page-item ${currentPage === page ? 'active' : ''}`}
|
||||
>
|
||||
<button
|
||||
className={`page-link shadow-sm rounded border-0 ${
|
||||
currentPage === page
|
||||
? ""
|
||||
: "bg-white text-primary"
|
||||
className={`page-link box p-0 ${
|
||||
currentPage === page ? 'active' : ''
|
||||
}`}
|
||||
onClick={() => handlePageChange(page)}
|
||||
>
|
||||
@@ -87,14 +83,14 @@ export default function Pagination({
|
||||
{/* Botón Siguiente */}
|
||||
<li
|
||||
className={`page-item ${
|
||||
currentPage === totalPages ? "disabled" : ""
|
||||
currentPage === totalPages ? 'disabled' : ''
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
className="page-link shadow-sm rounded border-0 bg-white"
|
||||
className='page-link box p-0'
|
||||
onClick={() => handlePageChange(currentPage + 1)}
|
||||
disabled={currentPage === totalPages}
|
||||
aria-label="Next"
|
||||
aria-label='Next'
|
||||
>
|
||||
»
|
||||
</button>
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
export const crear = {
|
||||
nombreFormulario:
|
||||
'Registro para la Feria de la Sexualidad - Comunidad FES Acatlán',
|
||||
descripcion:
|
||||
'La Feria de la Sexualidad es un espacio seguro e informativo dirigido a toda la comunidad universitaria. Este registro nos ayudará a organizar mejor las actividades, talleres y charlas, así como conocer tus intereses. Por favor, completa el siguiente formulario para confirmar tu participación.',
|
||||
banner: 'base64',
|
||||
secciones: [
|
||||
{
|
||||
titulo: 'Participación en actividades',
|
||||
descripcion:
|
||||
'Selecciona las actividades en las que te gustaría participar durante la Feria.',
|
||||
preguntas: [
|
||||
{
|
||||
titulo: '¿Qué tipo de actividades te interesan?',
|
||||
tipo: 'Multiple',
|
||||
opciones: [
|
||||
{ valor: 'Talleres sobre salud sexual' },
|
||||
{ valor: 'Charlas informativas' },
|
||||
{ valor: 'Mesas de diálogo y reflexión' },
|
||||
],
|
||||
obligatoria: true,
|
||||
},
|
||||
{
|
||||
titulo:
|
||||
'¿Estás interesado/a en recibir un certificado de participación? ',
|
||||
tipo: 'Cerrada',
|
||||
opciones: [{ valor: 'Si' }, { valor: 'No' }],
|
||||
obligatoria: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
titulo: 'Comentarios',
|
||||
descripcion:
|
||||
'Comparte tus ideas, inquietudes o sugerencias para esta y futuras ediciones',
|
||||
preguntas: [
|
||||
{
|
||||
titulo:
|
||||
'¿Hay algún tema específico que te gustaría que se abordara en la feria? ',
|
||||
tipo: 'Abierta',
|
||||
opciones: [],
|
||||
obligatoria: false,
|
||||
},
|
||||
{
|
||||
titulo: 'Comentarios adicionales',
|
||||
tipo: 'Abierta',
|
||||
opciones: [],
|
||||
obligatoria: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,195 @@
|
||||
export const res: CuestionarioCompleto = {
|
||||
tipo_cuestionario: {
|
||||
id_tipo_cuestionario: 1,
|
||||
tipo_cuestionario: 'Encuesta',
|
||||
},
|
||||
cuestionario: {
|
||||
id_cuestionario: 10,
|
||||
nombre_form: 'Encuesta de Satisfacción',
|
||||
contador_secciones: 2,
|
||||
descripcion: 'Encuesta para medir la satisfacción del cliente',
|
||||
editable: true,
|
||||
fecha_inicio: '2025-03-26T00:00:00',
|
||||
fecha_fin: '2025-03-31T23:59:59',
|
||||
id_cuestionario_original: null,
|
||||
id_tipo_cuestionario: 1,
|
||||
secciones: [
|
||||
{
|
||||
id_cuestionario_seccion: 1,
|
||||
posicion: 1,
|
||||
seccion: {
|
||||
id_seccion: 100,
|
||||
contador_pregunta: 2,
|
||||
descripcion: 'Preguntas generales sobre el servicio',
|
||||
titulo: 'General',
|
||||
},
|
||||
preguntas: [
|
||||
{
|
||||
id_seccion_pregunta: 1000,
|
||||
posicion: 1,
|
||||
pregunta: {
|
||||
id_pregunta: 10000,
|
||||
pregunta: '¿Cómo calificaría nuestro servicio?',
|
||||
contador_opcion: 4,
|
||||
obligatoria: true,
|
||||
id_tipo_pregunta: 1,
|
||||
id_opcion_dependiente: null,
|
||||
tipo_pregunta: {
|
||||
id_tipo: 1,
|
||||
tipo_pregunta: 'Cerrada',
|
||||
},
|
||||
opciones: [
|
||||
{
|
||||
id_pregunta_opcion: 50000,
|
||||
posicion: 1,
|
||||
id_opcion: 90000,
|
||||
opcion: {
|
||||
id_opcion: 90000,
|
||||
opcion: 'Excelente',
|
||||
},
|
||||
},
|
||||
{
|
||||
id_pregunta_opcion: 50001,
|
||||
posicion: 2,
|
||||
id_opcion: 90001,
|
||||
opcion: {
|
||||
id_opcion: 90001,
|
||||
opcion: 'Bueno',
|
||||
},
|
||||
},
|
||||
{
|
||||
id_pregunta_opcion: 50002,
|
||||
posicion: 3,
|
||||
id_opcion: 90002,
|
||||
opcion: {
|
||||
id_opcion: 90002,
|
||||
opcion: 'Regular',
|
||||
},
|
||||
},
|
||||
{
|
||||
id_pregunta_opcion: 50003,
|
||||
posicion: 4,
|
||||
id_opcion: 90003,
|
||||
opcion: {
|
||||
id_opcion: 90003,
|
||||
opcion: 'Malo',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id_seccion_pregunta: 1001,
|
||||
posicion: 2,
|
||||
pregunta: {
|
||||
id_pregunta: 10001,
|
||||
pregunta: '¿Recomendaría nuestro servicio?',
|
||||
contador_opcion: 2,
|
||||
obligatoria: true,
|
||||
id_tipo_pregunta: 1,
|
||||
id_opcion_dependiente: null,
|
||||
tipo_pregunta: {
|
||||
id_tipo: 1,
|
||||
tipo_pregunta: 'Cerrada',
|
||||
},
|
||||
opciones: [
|
||||
{
|
||||
id_pregunta_opcion: 50004,
|
||||
posicion: 1,
|
||||
id_opcion: 90004,
|
||||
opcion: {
|
||||
id_opcion: 90004,
|
||||
opcion: 'Sí',
|
||||
},
|
||||
},
|
||||
{
|
||||
id_pregunta_opcion: 50005,
|
||||
posicion: 2,
|
||||
id_opcion: 90005,
|
||||
opcion: {
|
||||
id_opcion: 90005,
|
||||
opcion: 'No',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id_cuestionario_seccion: 2,
|
||||
posicion: 2,
|
||||
seccion: {
|
||||
id_seccion: 101,
|
||||
contador_pregunta: 1,
|
||||
descripcion: 'Preguntas adicionales para conocer más detalles',
|
||||
titulo: 'Adicionales',
|
||||
},
|
||||
preguntas: [
|
||||
{
|
||||
id_seccion_pregunta: 1002,
|
||||
posicion: 1,
|
||||
pregunta: {
|
||||
id_pregunta: 10002,
|
||||
pregunta: '¿Qué mejorarías en nuestro servicio?',
|
||||
contador_opcion: 0,
|
||||
obligatoria: false,
|
||||
id_tipo_pregunta: 2,
|
||||
id_opcion_dependiente: null,
|
||||
tipo_pregunta: {
|
||||
id_tipo: 2,
|
||||
tipo_pregunta: 'Abierta',
|
||||
},
|
||||
opciones: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
id_seccion_pregunta: 1003,
|
||||
posicion: 3,
|
||||
pregunta: {
|
||||
id_pregunta: 10003,
|
||||
pregunta: '¿Qué aspectos del servicio fueron de tu agrado?',
|
||||
contador_opcion: 3,
|
||||
obligatoria: false,
|
||||
id_tipo_pregunta: 3, // Puedes usar este ID para distinguir múltiples
|
||||
id_opcion_dependiente: null,
|
||||
tipo_pregunta: {
|
||||
id_tipo: 3,
|
||||
tipo_pregunta: 'Multiple',
|
||||
},
|
||||
opciones: [
|
||||
{
|
||||
id_pregunta_opcion: 50006,
|
||||
posicion: 1,
|
||||
id_opcion: 90006,
|
||||
opcion: {
|
||||
id_opcion: 90006,
|
||||
opcion: 'Rapidez',
|
||||
},
|
||||
},
|
||||
{
|
||||
id_pregunta_opcion: 50007,
|
||||
posicion: 2,
|
||||
id_opcion: 90007,
|
||||
opcion: {
|
||||
id_opcion: 90007,
|
||||
opcion: 'Atención al cliente',
|
||||
},
|
||||
},
|
||||
{
|
||||
id_pregunta_opcion: 50008,
|
||||
posicion: 3,
|
||||
id_opcion: 90008,
|
||||
opcion: {
|
||||
id_opcion: 90008,
|
||||
opcion: 'Facilidad de uso',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -1,8 +1,8 @@
|
||||
.box {
|
||||
@extend .shadow-sm;
|
||||
@extend .border;
|
||||
@extend .rounded;
|
||||
@extend .bg-white;
|
||||
@extend .p-3;
|
||||
@extend .p-2;
|
||||
|
||||
&.active {
|
||||
@extend .bg-primary;
|
||||
|
||||
Vendored
+70
@@ -0,0 +1,70 @@
|
||||
interface TipoCuestionario {
|
||||
id_tipo_cuestionario: number;
|
||||
tipo_cuestionario: string;
|
||||
}
|
||||
|
||||
interface Opcion {
|
||||
id_opcion: number;
|
||||
opcion: string;
|
||||
}
|
||||
|
||||
interface PreguntaOpcion {
|
||||
id_pregunta_opcion: number;
|
||||
posicion: number;
|
||||
id_opcion: number;
|
||||
opcion: Opcion;
|
||||
}
|
||||
|
||||
interface TipoPregunta {
|
||||
id_tipo: number;
|
||||
tipo_pregunta: string;
|
||||
}
|
||||
|
||||
interface Pregunta {
|
||||
id_pregunta: number;
|
||||
pregunta: string;
|
||||
contador_opcion: number;
|
||||
obligatoria: boolean;
|
||||
id_tipo_pregunta: number;
|
||||
id_opcion_dependiente: number | null;
|
||||
tipo_pregunta: TipoPregunta;
|
||||
opciones: PreguntaOpcion[];
|
||||
}
|
||||
|
||||
interface SeccionPregunta {
|
||||
id_seccion_pregunta: number;
|
||||
posicion: number;
|
||||
pregunta: Pregunta;
|
||||
}
|
||||
|
||||
interface SeccionInfo {
|
||||
id_seccion: number;
|
||||
contador_pregunta: number;
|
||||
descripcion: string;
|
||||
titulo: string;
|
||||
}
|
||||
|
||||
interface CuestionarioSeccion {
|
||||
id_cuestionario_seccion: number;
|
||||
posicion: number;
|
||||
seccion: SeccionInfo;
|
||||
preguntas: SeccionPregunta[];
|
||||
}
|
||||
|
||||
interface Cuestionario {
|
||||
id_cuestionario: number;
|
||||
nombre_form: string;
|
||||
contador_secciones: number;
|
||||
descripcion: string;
|
||||
editable: boolean;
|
||||
fecha_inicio: string; // Formato ISO 8601
|
||||
fecha_fin: string; // Formato ISO 8601
|
||||
id_cuestionario_original: number | null;
|
||||
id_tipo_cuestionario: number;
|
||||
secciones: CuestionarioSeccion[];
|
||||
}
|
||||
|
||||
interface CuestionarioCompleto {
|
||||
tipo_cuestionario: TipoCuestionario;
|
||||
cuestionario: Cuestionario;
|
||||
}
|
||||
Reference in New Issue
Block a user