diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..936c6c2 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,11 @@ +{ + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "useTabs": false, + "trailingComma": "es5", + "printWidth": 80, + "bracketSpacing": true, + "arrowParens": "always", + "endOfLine": "lf" +} diff --git a/package-lock.json b/package-lock.json index 2e868b4..75f04d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,14 @@ "name": "formularios_front", "version": "0.1.0", "dependencies": { + "@yudiel/react-qr-scanner": "^2.2.1", "axios": "^1.8.4", "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" + "react-dom": "^19.0.0", + "react-hot-toast": "^2.5.2" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -1215,6 +1216,12 @@ "@popperjs/core": "^2.9.2" } }, + "node_modules/@types/emscripten": { + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.40.1.tgz", + "integrity": "sha512-sr53lnYkQNhjHNN0oJDdUm5564biioI5DuOpycufDVK7D3y+GR3oUswe2rlwY1nPNyusHbrJ9WoTyIHl4/Bpwg==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", @@ -1715,6 +1722,20 @@ "win32" ] }, + "node_modules/@yudiel/react-qr-scanner": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@yudiel/react-qr-scanner/-/react-qr-scanner-2.2.1.tgz", + "integrity": "sha512-5tmzoi9d8mqqaxKTxfI9kulS3N3Kph75AOqwU2lnl4IMwjmLcTXS8IyecDUvzFNuoMNhCpofrqqGueXm8IXkjA==", + "license": "MIT", + "dependencies": { + "barcode-detector": "^3.0.0", + "webrtc-adapter": "9.0.1" + }, + "peerDependencies": { + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19" + } + }, "node_modules/acorn": { "version": "8.14.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", @@ -2023,6 +2044,15 @@ "dev": true, "license": "MIT" }, + "node_modules/barcode-detector": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/barcode-detector/-/barcode-detector-3.0.1.tgz", + "integrity": "sha512-3fCzG/Py4SVgZJhubD1mt7rVprtHEVWrxQN4FUOG0oulPE4193evbgyptxcOYsfTNEtMlWc+Ec9tdxhjlR4/Ww==", + "license": "MIT", + "dependencies": { + "zxing-wasm": "^2.1.0" + } + }, "node_modules/bootstrap": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", @@ -2294,7 +2324,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -3408,6 +3437,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/goober": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz", + "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -3518,12 +3556,6 @@ "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", @@ -4699,6 +4731,23 @@ "react": "^19.1.0" } }, + "node_modules/react-hot-toast": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.2.tgz", + "integrity": "sha512-Tun3BbCxzmXXM7C+NI4qiv6lT0uwGh4oAfeJyNOjYUejTsm35mK9iCaYLGv8cBz9L5YxZLx/2ii7zsIwPtPUdw==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.3", + "goober": "^2.1.16" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -4922,6 +4971,12 @@ "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", "license": "MIT" }, + "node_modules/sdp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.0.tgz", + "integrity": "sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw==", + "license": "MIT" + }, "node_modules/semver": { "version": "7.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", @@ -5445,6 +5500,18 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "4.39.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.39.1.tgz", + "integrity": "sha512-uW9qzd66uyHYxwyVBYiwS4Oi0qZyUqwjU+Oevr6ZogYiXt99EOYtwvzMSLw1c3lYo2HzJsep/NB23iEVEgjG/w==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -5600,6 +5667,19 @@ "punycode": "^2.1.0" } }, + "node_modules/webrtc-adapter": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-9.0.1.tgz", + "integrity": "sha512-1AQO+d4ElfVSXyzNVTOewgGT/tAomwwztX/6e3totvyyzXPvXIIuUUjAmyZGbKBKbZOXauuJooZm3g6IuFuiNQ==", + "license": "BSD-3-Clause", + "dependencies": { + "sdp": "^3.2.0" + }, + "engines": { + "node": ">=6.0.0", + "npm": ">=3.10.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5727,6 +5807,19 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zxing-wasm": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/zxing-wasm/-/zxing-wasm-2.1.0.tgz", + "integrity": "sha512-CvuwDZHRHwg6PeCARaCDIp3dauD4cin0mbHrQQZtMDrr5mblPzCimAjdw/XMhD/Au10q/f5+SAupvYqYvUOg1Q==", + "license": "MIT", + "dependencies": { + "@types/emscripten": "^1.40.0", + "type-fest": "^4.35.0" + }, + "peerDependencies": { + "@types/emscripten": ">=1.39.6" + } } } } diff --git a/package.json b/package.json index 8442740..b0ba5d4 100644 --- a/package.json +++ b/package.json @@ -9,13 +9,14 @@ "lint": "next lint" }, "dependencies": { + "@yudiel/react-qr-scanner": "^2.2.1", "axios": "^1.8.4", "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" + "react-dom": "^19.0.0", + "react-hot-toast": "^2.5.2" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/public/DOCUMENTACION GUAPA.pdf b/public/DOCUMENTACION GUAPA.pdf deleted file mode 100644 index c086aba..0000000 Binary files a/public/DOCUMENTACION GUAPA.pdf and /dev/null differ diff --git a/public/Linux Compilación _250326_214950.pdf b/public/Linux Compilación _250326_214950.pdf deleted file mode 100644 index 6fab36e..0000000 Binary files a/public/Linux Compilación _250326_214950.pdf and /dev/null differ diff --git a/public/feria.png b/public/banner1.png similarity index 100% rename from public/feria.png rename to public/banner1.png diff --git a/public/banner2.png b/public/banner2.png new file mode 100644 index 0000000..5f83af9 Binary files /dev/null and b/public/banner2.png differ diff --git a/src/app/(admins)/administrador/crear-formulario/page.tsx b/src/app/(admins)/administrador/crear-formulario/page.tsx index b04c25d..d08a131 100644 --- a/src/app/(admins)/administrador/crear-formulario/page.tsx +++ b/src/app/(admins)/administrador/crear-formulario/page.tsx @@ -7,18 +7,20 @@ import Select from '@/components/select'; import Input from '@/components/input'; import Button from '@/components/button'; import FloatingInput from '@/components/floating-input'; -import { Pregunta, Seccion } from '@/types/cuestionario'; import { plantillasDisponibles } from '@/data/plantillas'; +import { FormularioCreacion, PreguntaFormulario, SeccionFormulario } from '@/types/crear-formulario'; +import toast from 'react-hot-toast'; export default function AdminFormPage() { const [nombreFormulario, setNombreFormulario] = useState(''); const [descripcion, setDescripcion] = useState(''); - const [secciones, setSecciones] = useState([]); + const [secciones, setSecciones] = useState([]); const [paginaActual, setPaginaActual] = useState(1); const [bannerPreview, setBannerPreview] = useState(null); const [tipoCuestionario, setTipoCuestionario] = useState(null); const [fechaInicio, setFechaInicio] = useState(null); const [fechaFin, setFechaFin] = useState(null); + const [loading, setLoading] = useState(false); const swapItems = (arr: T[], index1: number, index2: number): T[] => { const result = [...arr]; @@ -58,10 +60,10 @@ export default function AdminFormPage() { ); }; - const actualizarSeccion = ( + const actualizarSeccion = ( index: number, campo: K, - valor: Seccion[K] + valor: SeccionFormulario[K] ) => { setSecciones((prev) => { const nuevas = [...prev]; @@ -90,8 +92,8 @@ export default function AdminFormPage() { const actualizarPregunta = ( seccionIndex: number, preguntaIndex: number, - campo: keyof Pregunta, - valor: Pregunta[keyof Pregunta] + campo: keyof PreguntaFormulario, + valor: PreguntaFormulario[keyof PreguntaFormulario] ) => { setSecciones((prev) => { const nuevas = [...prev]; @@ -103,12 +105,11 @@ export default function AdminFormPage() { return nuevas; }); }; + const agregarOpcion = (seccionIndex: number, preguntaIndex: number) => { const nuevas = [...secciones]; if (nuevas[seccionIndex]?.preguntas[preguntaIndex]?.opciones) { - nuevas[seccionIndex].preguntas[preguntaIndex].opciones.push({ - valor: '', - }); + nuevas[seccionIndex].preguntas[preguntaIndex].opciones!.push({ valor: '' }); } setSecciones(nuevas); }; @@ -135,18 +136,16 @@ export default function AdminFormPage() { const nuevas = [...secciones]; if ( nuevas[seccionIndex]?.preguntas[preguntaIndex]?.opciones && - nuevas[seccionIndex].preguntas[preguntaIndex].opciones[opcionIndex] + nuevas[seccionIndex].preguntas[preguntaIndex].opciones![opcionIndex] ) { - nuevas[seccionIndex].preguntas[preguntaIndex].opciones[ - opcionIndex - ].valor = valor; + nuevas[seccionIndex].preguntas[preguntaIndex].opciones![opcionIndex].valor = valor; } setSecciones(nuevas); }; const guardarFormulario = async () => { - if (fechaInicio === null || fechaFin === null) { - alert('Las fechas son obligatorias'); + if (!fechaInicio || !fechaFin || !tipoCuestionario) { + alert('Todos los campos obligatorios deben estar llenos'); return; } @@ -155,27 +154,35 @@ export default function AdminFormPage() { return `${fecha}${sufijo}`; }; - const formData = { + const formData: FormularioCreacion = { nombre_form: nombreFormulario, - id_tipo_cuestionario: tipoCuestionario, + descripcion, fecha_inicio: formatearFecha(fechaInicio, 'inicio'), fecha_fin: formatearFecha(fechaFin, 'fin'), - descripcion, + id_tipo_cuestionario: tipoCuestionario, + evento: nombreFormulario, // temporalmente igual al nombre del form secciones, }; console.log('Formulario a guardar:', formData); - try { - const res = await axiosInstance.post('/cuestionario', formData); + setLoading(true); + await toast.promise( + axiosInstance.post('/cuestionario', formData), + { + loading: 'Guardando formulario...', + success: 'Formulario guardado exitosamente!', + error: 'Error al guardar el formulario.', + } + ).then((res) => { console.log('Formulario guardado:', res.data); - } catch (error) { + }).catch((error) => { console.error('Error al guardar el formulario:', error); - } + }).finally(() => { + setLoading(false); + }); }; - const seccionActual = secciones[paginaActual - 1]; - const handleBannerChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0] || null; if (file) { @@ -187,67 +194,76 @@ export default function AdminFormPage() { } }; + const seccionActual = secciones[paginaActual - 1]; + return ( <> -

Crear nuevo formulario

+

Crear nuevo formulario

- {bannerPreview && ( -
+
Ejemplo de banner setNombreFormulario(e.target.value)} - placeholder='Ingrese el nombre del formulario' + placeholder="Ingrese el nombre del formulario" /> -
- +
+