Lectura qr
This commit is contained in:
Generated
+18
-1
@@ -10,12 +10,14 @@
|
||||
"dependencies": {
|
||||
"bootstrap": "^5.3.3",
|
||||
"bootstrap-icons": "^1.11.3",
|
||||
"html5-qrcode": "^2.3.8",
|
||||
"next": "15.2.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@types/bootstrap": "^5.2.10",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
@@ -1157,7 +1159,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
@@ -1203,6 +1204,16 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/bootstrap": {
|
||||
"version": "5.2.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.2.10.tgz",
|
||||
"integrity": "sha512-F2X+cd6551tep0MvVZ6nM8v7XgGN/twpdNDjqS1TUM7YFNEtQYWk+dKAnH+T1gr6QgCoGMPl487xw/9hXooa2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.9.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
|
||||
@@ -3446,6 +3457,12 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/html5-qrcode": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/html5-qrcode/-/html5-qrcode-2.3.8.tgz",
|
||||
"integrity": "sha512-jsr4vafJhwoLVEDW3n1KvPnCCXWaQfRng0/EEYk1vNcQGcG/htAdhJX0be8YyqMoSz7+hZvOZSTAepsabiuhiQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||
|
||||
@@ -11,12 +11,14 @@
|
||||
"dependencies": {
|
||||
"bootstrap": "^5.3.3",
|
||||
"bootstrap-icons": "^1.11.3",
|
||||
"html5-qrcode": "^2.3.8",
|
||||
"next": "15.2.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@types/bootstrap": "^5.2.10",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import Footer from '@/components/layout/footer';
|
||||
import Header from '@/components/layout/header';
|
||||
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='flex-grow-1 d-flex flex-column justify-content-center align-items-center'>
|
||||
<div className='container'>{children}</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function Page() {
|
||||
return <div>Page</div>;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
'use client';
|
||||
import Button from '@/components/button';
|
||||
import Input from '@/components/input';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import React from 'react';
|
||||
|
||||
export default function Page() {
|
||||
const router = useRouter();
|
||||
|
||||
const handleOnSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
// Aquí puedes agregar la lógica para manejar el inicio de sesión
|
||||
// Por ejemplo, validar las credenciales y redirigir al usuario
|
||||
router.push('/staff');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='d-flex flex-column justify-content-center align-items-center'>
|
||||
<h1 className='text-dorado'>Iniciar sesión</h1>
|
||||
<h2 className='text-azul'>Staff</h2>
|
||||
|
||||
<form className='w-300px' onSubmit={handleOnSubmit}>
|
||||
<Input label='Usuario' />
|
||||
<Input type='password' label='Contraseña' />
|
||||
<div className='mb-3'>
|
||||
<Button className='w-100' type='submit'>
|
||||
Iniciar sesión
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -206,25 +206,6 @@ export default function AdminFormPage() {
|
||||
)}
|
||||
</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'>
|
||||
@@ -271,6 +252,26 @@ export default function AdminFormPage() {
|
||||
</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'>
|
||||
<button
|
||||
className='btn btn-outline-primary mb-3'
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
'use client';
|
||||
|
||||
import Header from '@/components/layout/header';
|
||||
import QrScanner from '@/components/qr-scanner';
|
||||
import dynamic from 'next/dynamic';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const Modal = dynamic(() => import('@/components/modal'), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
export default function Page() {
|
||||
const [resultado, setResultado] = useState<string | null>(null);
|
||||
const [mostrarModal, setMostrarModal] = useState(false);
|
||||
|
||||
const handleScan = (text: string) => {
|
||||
setResultado(text);
|
||||
setMostrarModal(true);
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
// Aquí haces algo con el QR (guardar, redirigir, etc)
|
||||
alert('Confirmado: ' + resultado);
|
||||
setMostrarModal(false);
|
||||
setResultado(null); // Limpiar para volver a escanear
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='d-flex flex-column min-vh-100'>
|
||||
<Header small />
|
||||
<main className='py-3'>
|
||||
<div className='container text-center'>
|
||||
<h1 className='mb-4'>Lectura de QRs</h1>
|
||||
{!resultado && <QrScanner onScan={handleScan} />}
|
||||
|
||||
<Modal
|
||||
isVisible={mostrarModal}
|
||||
onClose={() => setMostrarModal(false)}
|
||||
size='md'
|
||||
>
|
||||
<h5 className='mb-3'>Resultado del QR</h5>
|
||||
<pre className='bg-light p-2 rounded'>{resultado}</pre>
|
||||
<button className='btn btn-primary mt-3' onClick={handleConfirm}>
|
||||
Confirmar
|
||||
</button>
|
||||
</Modal>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
'use client';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { Modal as BootstrapModal } from 'bootstrap';
|
||||
|
||||
interface ModalProps {
|
||||
children?: React.ReactNode;
|
||||
isVisible: boolean;
|
||||
closeButton?: boolean;
|
||||
size: 'sm' | 'md' | 'lg' | 'xl';
|
||||
className?: {
|
||||
dialog?: string;
|
||||
content?: string;
|
||||
body?: string;
|
||||
};
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export default function Modal({
|
||||
children,
|
||||
isVisible,
|
||||
size,
|
||||
className,
|
||||
closeButton = true,
|
||||
onClose,
|
||||
}: ModalProps) {
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
const modalInstance = useRef<BootstrapModal | null>(null);
|
||||
const modalSizeClass = size ? `modal-${size}` : '';
|
||||
|
||||
useEffect(() => {
|
||||
const node = ref.current;
|
||||
|
||||
if (node) {
|
||||
modalInstance.current = new BootstrapModal(node);
|
||||
|
||||
const handleClose = () => {
|
||||
const elements = document.querySelectorAll<HTMLDivElement>(
|
||||
'.modal-backdrop.fade.show'
|
||||
);
|
||||
if (elements.length > 0) {
|
||||
elements.forEach((element) => {
|
||||
element.remove();
|
||||
});
|
||||
document.body.removeAttribute('style');
|
||||
}
|
||||
onClose();
|
||||
};
|
||||
|
||||
node.addEventListener('hidden.bs.modal', handleClose);
|
||||
|
||||
return () => {
|
||||
if (node) {
|
||||
node.removeEventListener('hidden.bs.modal', handleClose);
|
||||
}
|
||||
};
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isVisible) {
|
||||
modalInstance.current?.show();
|
||||
} else {
|
||||
modalInstance.current?.hide();
|
||||
}
|
||||
}, [isVisible]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="modal fade"
|
||||
tabIndex={-1}
|
||||
aria-labelledby="modal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
className={`modal-dialog modal-dialog-centered ${className?.dialog ?? ''} ${modalSizeClass}`}
|
||||
>
|
||||
<div className={`modal-content ${className?.content}`}>
|
||||
{closeButton && (
|
||||
<div className="position-relative">
|
||||
<button
|
||||
type="button"
|
||||
className="btn-close position-absolute top-0 end-0 m-2"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
style={{
|
||||
zIndex: 999,
|
||||
}}
|
||||
></button>
|
||||
</div>
|
||||
)}
|
||||
<div className={`modal-body ${className?.body}`}>{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// components/QrScanner.tsx
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { Html5Qrcode } from 'html5-qrcode';
|
||||
|
||||
interface Props {
|
||||
onScan: (text: string) => void;
|
||||
}
|
||||
|
||||
export default function QrScanner({ onScan }: Props) {
|
||||
useEffect(() => {
|
||||
const scanner = new Html5Qrcode('qr-reader');
|
||||
|
||||
scanner
|
||||
.start(
|
||||
{ facingMode: 'environment' },
|
||||
{ fps: 10, qrbox: 250 },
|
||||
(decodedText) => {
|
||||
onScan(decodedText);
|
||||
scanner.stop().then(() => scanner.clear());
|
||||
},
|
||||
(error) => {
|
||||
// puedes ignorar errores de escaneo frecuentes
|
||||
console.error('Error de escaneo:', error);
|
||||
}
|
||||
)
|
||||
.catch((err) => console.error('Error al iniciar el lector QR:', err));
|
||||
|
||||
return () => {
|
||||
scanner.stop().then(() => scanner.clear());
|
||||
};
|
||||
}, [onScan]);
|
||||
|
||||
return (
|
||||
<div
|
||||
id='qr-reader'
|
||||
style={{ width: '100%', maxWidth: 400, margin: '0 auto' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,51 +1,51 @@
|
||||
export const crear = {
|
||||
nombreFormulario:
|
||||
'Registro para la Feria de la Sexualidad - Comunidad FES Acatlán',
|
||||
export const feriaSexualidad = {
|
||||
nombre_form: 'Registro para la Feria de la Sexualidad - 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',
|
||||
fecha_inicio: '2025-03-07T00:00:01',
|
||||
fecha_fin: '2025-03-18T23:59:59',
|
||||
id_tipo_cuestionario: 1,
|
||||
secciones: [
|
||||
{
|
||||
titulo: 'Participación en actividades',
|
||||
titulo: 'Información Personal',
|
||||
descripcion:
|
||||
'Selecciona las actividades en las que te gustaría participar durante la Feria.',
|
||||
'Proporciona tus datos personales para poder contactarte y enviarte información relevante sobre la Feria de la Sexualidad.',
|
||||
preguntas: [
|
||||
{
|
||||
titulo: '¿Qué tipo de actividades te interesan?',
|
||||
titulo: '¿Eres parte de la comunidad de la FES Acatlán?',
|
||||
tipo: 'Multiple',
|
||||
opciones: [
|
||||
{ valor: 'Talleres sobre salud sexual' },
|
||||
{ valor: 'Charlas informativas' },
|
||||
{ valor: 'Mesas de diálogo y reflexión' },
|
||||
],
|
||||
opciones: [{ valor: 'Si' }, { valor: 'No' }],
|
||||
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: 'Numero de cuenta',
|
||||
obligatoria: true,
|
||||
tipo: 'Numero',
|
||||
},
|
||||
{
|
||||
titulo: 'Comentarios adicionales',
|
||||
tipo: 'Abierta',
|
||||
opciones: [],
|
||||
obligatoria: false,
|
||||
titulo: 'Nombre(s)',
|
||||
obligatoria: true,
|
||||
tipo: 'Texto',
|
||||
},
|
||||
{
|
||||
titulo: 'Apellidos',
|
||||
obligatoria: true,
|
||||
tipo: 'Texto',
|
||||
},
|
||||
{
|
||||
titulo: 'genero',
|
||||
obligatoria: true,
|
||||
tipo: 'Radio',
|
||||
opciones: [
|
||||
{ valor: 'Masculino' },
|
||||
{ valor: 'Femenino' },
|
||||
{ valor: 'Prefiero no decirlo' },
|
||||
],
|
||||
},
|
||||
{
|
||||
titulo: 'Institución de procedencia',
|
||||
tipo: 'Abierto',
|
||||
obligatoria: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -5,9 +5,11 @@ export const res: CuestionarioCompleto = {
|
||||
},
|
||||
cuestionario: {
|
||||
id_cuestionario: 10,
|
||||
nombre_form: 'Encuesta de Satisfacción',
|
||||
nombre_form:
|
||||
'Registro para la Feria de la Sexualidad - Comunidad FES Acatlán',
|
||||
contador_secciones: 2,
|
||||
descripcion: 'Encuesta para medir la satisfacción del cliente',
|
||||
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.',
|
||||
editable: true,
|
||||
fecha_inicio: '2025-03-26T00:00:00',
|
||||
fecha_fin: '2025-03-31T23:59:59',
|
||||
@@ -20,7 +22,7 @@ export const res: CuestionarioCompleto = {
|
||||
seccion: {
|
||||
id_seccion: 100,
|
||||
contador_pregunta: 2,
|
||||
descripcion: 'Preguntas generales sobre el servicio',
|
||||
descripcion: 'Preguntas generales',
|
||||
titulo: 'General',
|
||||
},
|
||||
preguntas: [
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/* const comunidad = {
|
||||
id_formulario: 1,
|
||||
respuestas: [
|
||||
{ id_pregunta: 101, valor: 'Si' }, // ¿Eres parte de la comunidad de la FES Acatlán?
|
||||
{ id_pregunta: 102, valor: '123456789' }, // Numero de cuenta
|
||||
{ id_pregunta: 106, valor: 'Prefiero no decirlo' },
|
||||
],
|
||||
fecha_envio: '2025-04-02T13:45:00',
|
||||
};
|
||||
|
||||
const externos = {
|
||||
id_formulario: 1,
|
||||
respuestas: [
|
||||
{ id_pregunta: 101, valor: 'No' }, // ¿Eres parte de la comunidad de la FES Acatlán?
|
||||
{ id_pregunta: 103, valor: 'Juan Carlos' }, // Nombre(s)
|
||||
{ id_pregunta: 104, valor: 'Ramírez López' }, // Apellidos
|
||||
{ id_pregunta: 106, valor: 'Prefiero no decirlo' }, // genero
|
||||
{ id_pregunta: 107, valor: 'UAM Azcapotzalco' }, // Institución de procedencia
|
||||
],
|
||||
fecha_envio: '2025-04-02T13:45:00',
|
||||
};
|
||||
*/
|
||||
@@ -5,7 +5,7 @@ $hover-scales: (
|
||||
2: 1.1,
|
||||
3: 1.15,
|
||||
4: 1.2,
|
||||
5: 1.25
|
||||
5: 1.25,
|
||||
);
|
||||
|
||||
@each $key, $value in $hover-scales {
|
||||
@@ -17,3 +17,13 @@ $hover-scales: (
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$min-width: 100px;
|
||||
$max-width: 600px;
|
||||
|
||||
@for $i from 1 through (($max-width - $min-width) / 100px + 1) {
|
||||
$current-width: $min-width + ($i - 1) * 100px;
|
||||
.w-#{$current-width} {
|
||||
width: $current-width;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user