diff --git a/cosiap_api/modalidades/serializers.py b/cosiap_api/modalidades/serializers.py index 21428b76dc8eb1acaf310baeaef1a2dec0e6432e..a759fcd1b0cf7198fc1c20e45afb612fc4b19b4f 100644 --- a/cosiap_api/modalidades/serializers.py +++ b/cosiap_api/modalidades/serializers.py @@ -2,7 +2,6 @@ from rest_framework import serializers from modalidades.models import Modalidad class ModalidadSerializer(serializers.ModelSerializer): - monto = serializers.FloatField(write_only=True) class Meta: model = Modalidad diff --git a/cosiap_frontend/src/App.jsx b/cosiap_frontend/src/App.jsx index 87450145e3aa1d3a6643719487a59744b131336c..e20c2f07f34332d9df1c1b4070d2cde4c4917b1e 100644 --- a/cosiap_frontend/src/App.jsx +++ b/cosiap_frontend/src/App.jsx @@ -24,6 +24,7 @@ import { useState } from "react"; import LayoutBaseAuthenticator from "@/components/common/layouts/LayoutBaseAuthenticator"; import LayoutBaseNavigation from "@/components/common/layouts/LayoutBaseNavigation"; import Modalidades from "@/components/modalidades/Modalidades"; +import CreateModalidad from "./components/modalidades/CrearModalidad"; function App() { const [viewPageLoader, setViewPageLoader] = useState(false); @@ -105,6 +106,7 @@ function RoutesApp({ setViewPageLoader }) { > } /> } /> + } /> diff --git a/cosiap_frontend/src/api.js b/cosiap_frontend/src/api.js index 824fdc9ecdce79c7615d7f7448bba0d9cd10c5c5..839e8af60b736ea23238f031127bcde8f05718d1 100644 --- a/cosiap_frontend/src/api.js +++ b/cosiap_frontend/src/api.js @@ -118,6 +118,43 @@ const api = { post: (data) => ax.post('api/dynamic-tables/', data), getById: (id) => ax.get(`api/dynamic-tables/${id}`), update: (id, data) => ax.put(`api/dynamic-tables/${id}`, data), + }, + dynamicForms: { + opciones: { + get: () => ax.get('api/formularios/opciones/'), + post: (data) => ax.post('api/formularios/opciones/', data), + getById: (id) => ax.get(`api/formularios/opciones/${id}`), + update: (id, data) => ax.put(`api/formularios/opciones/${id}`, data), + }, + elementos: { + get: () => ax.get('api/formularios/elementos'), + post: (data) => ax.post('api/formularios/elementos/', data), + getById: (id) => ax.get(`api/formularios/elementos/${id}`), + update: (id, data) => ax.put(`api/formularios/elementos/${id}`, data), + postElementOption: (elementId, optionId) => ax.post(`/api/formularios/elementos/${elementId}/opcion/${optionId}/`), + }, + secciones: { + get: () => ax.get('api/formularios/secciones'), + post: (data) => ax.post('api/formularios/secciones/', data), + getById: (id) => ax.get(`api/formularios/secciones/${id}`), + update: (id, data) => ax.put(`api/formularios/secciones/${id}`, data), + postSectionElement: (sectionId, elementId) => ax.post(`/api/formularios/secciones/${sectionId}/elementos/${elementId}`), + }, + dynamicForms: { + get: () => ax.get('api/formularios'), + post: (data) => ax.post('api/formularios/', data), + getById: (id) => ax.get(`api/formularios/${id}`), + update: (id, data) => ax.put(`api/formularios/${id}`, data), + postFormSection: (formId, sectionId) => ax.post(`/api/formularios/${formId}/secciones/${sectionId}`), + }, + respuestas: { + get: () => ax.get('api/formularios/respuestas'), + post: (data) => ax.post('api/formularios/respuestas/', data), + getById: (id) => ax.get(`api/formularios/respuestas/${id}`), + }, + }, + formatos: { + get: () => ax.get('api/plantillas') } }; diff --git a/cosiap_frontend/src/components/modalidades/CrearModalidad.css b/cosiap_frontend/src/components/modalidades/CrearModalidad.css new file mode 100644 index 0000000000000000000000000000000000000000..4a0598fcf12b288ae21b287595297b74e22062ee --- /dev/null +++ b/cosiap_frontend/src/components/modalidades/CrearModalidad.css @@ -0,0 +1,160 @@ +/* Estilos para el card blanco */ +.white-card { + background-color: white; + padding: 20px; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + margin-bottom: 20px; +} + +/* Estilos para las filas del formulario */ +.form-row { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + margin-bottom: 15px; +} + +.form-group { + flex: 1; + margin-right: 15px; + margin-bottom: 15px; +} + +.form-group label { + display: block; + font-weight: bold; + margin-bottom: 5px; +} + +.form-group input, +.form-group textarea, +.form-group select { + width: 100%; + padding: 8px; + border: 1px solid #ccc; + border-radius: 4px; + font-size: 16px; +} + +.form-group input[type="file"] { + padding: 0; +} + +textarea { + resize: vertical; +} + +/* Estilos para secciones */ +.section { + background-color: #f9f9f9; + padding: 15px; + margin-bottom: 20px; + border-radius: 8px; + border: 1px solid #ddd; +} + +/* Estilos para elementos */ +.element { + background-color: #f2f2f2; + padding: 10px; + margin-bottom: 10px; + border-radius: 6px; + border: 1px solid #ccc; +} + +/* Estilos para opciones */ +.option { + background-color: #e9e9e9; + padding: 8px; + margin-bottom: 8px; + border-radius: 4px; + border: 1px solid #bbb; +} + +/* Botones */ +.add-button { + background-color: #696d69; + color: white; + border: none; + padding: 8px 12px; + font-size: 14px; + border-radius: 16px; + cursor: pointer; + margin-top: 10px; + margin-right: 10px; +} + +.add-button:hover { + background-color: #45a049; +} + +.delete-button { + background-color: #f44336; + color: white; + border: none; + padding: 8px 12px; + font-size: 14px; + border-radius: 16px; + cursor: pointer; + margin-top: 10px; + margin-right: 10px; +} + +.delete-button:hover { + background-color: #e53935; +} + +/* Botón de envío */ +.submit-button { + background-color: #4fb659; + color: white; + padding: 10px 20px; + font-size: 16px; + border: none; + border-radius: 16px; + cursor: pointer; + margin-top: 20px; +} + +.submit-button:hover { + background-color: #19d2b0; +} + +/* Checkbox más pequeño */ +input[type="checkbox"] { + width: 20px; + height: 20px; + cursor: pointer; + margin-right: 10px; +} + + +.subtitle-text { + font-size: 20px; + color: #c65f5f; +} + +.subtitle-text-1 { + font-size: 18px; + color: #752323; +} + +.subtitle-text-2 { + font-size: 16px; + color: #9e5f5f; +} + +/* Ajustar márgenes y tamaño del texto en etiquetas */ +.form-group label { + font-size: 16px; + color: #333; + margin-bottom: 8px; +} + +.button-container { + display: flex; + justify-content: space-between; + margin-top: 20px; + padding: 0 20px; +} diff --git a/cosiap_frontend/src/components/modalidades/CrearModalidad.jsx b/cosiap_frontend/src/components/modalidades/CrearModalidad.jsx new file mode 100644 index 0000000000000000000000000000000000000000..fb4ea6c85d80e34ef0c1261fa699c654231173c4 --- /dev/null +++ b/cosiap_frontend/src/components/modalidades/CrearModalidad.jsx @@ -0,0 +1,461 @@ +import { useState, useEffect } from "react"; +import api from '../../api'; +import './CrearModalidad.css' +import MainContainer from "../common/utility/MainContainer"; + +// componente para la creación de una modalidad +// incluyendo la estructura de su formulario dinámico +const CreateModalidad = () => { + const [nombre, setNombre] = useState(''); + const [descripcion, setDescripcion] = useState(''); + const [montoMaximo, setMontoMaximo] = useState(0); + const [mostrar, setMostrar] = useState(true); + const [archivado, setArchivado] = useState(false); + const [imagen, setImagen] = useState(null); + const [formName, setFormName] = useState(''); + const [sections, setSections] = useState([]); + const [formatos, setFormatos] = useState([]); + + // Obtener los formatos disponibles al cargar el componente + useEffect(() => { + const fetchFormatos = async () => { + try { + const response = await api.formatos.get(); + console.log(response.data); // Verifica la estructura de la respuesta + setFormatos(response.data || []); // Asegúrate de que `data` sea un array + } catch (error) { + console.error("Error al obtener los formatos", error); + setFormatos([]); + } + }; + fetchFormatos(); + }, []); + + + // manejamos los cambios de estado de las imagenes. + const handleImageChange = (e) => { + setImagen(e.target.files[0]); + }; + + // agregar una seccion a el form + const addSection = () => { + setSections([...sections, {name:'', elements:[]}]); + }; + + // actualizar el nombre de una sección + const updateSectionName = (index, name) => { + const newSections = [...sections]; + newSections[index].name = name; + setSections(newSections); + }; + + // Eliminar una sección del formulario + const removeSection = (index) => { + const newSections = sections.filter((_, i) => i !== index); + setSections(newSections); + }; + + // Actualizar el orden de una sección + const updateSectionOrden = (index, orden) => { + const newSections = [...sections]; + newSections[index].orden = Number(orden); // Convertir a número + setSections(newSections); + }; + + // Añadir un elemento a una sección + const addElementToSection = (sectionIndex) => { + const newElement = {name:'', type:'texto_corto', options:[]}; + const newSections = [...sections]; + newSections[sectionIndex].elements.push(newElement); + setSections(newSections); + }; + + // actualizar un elemento + const updateElement = (sectionIndex, elementIndex, key, value) => { + const newSections = [...sections]; + if (key === 'orden') { + newSections[sectionIndex].elements[elementIndex][key] = Number(value); // Convertir a número + } else { + newSections[sectionIndex].elements[elementIndex][key] = value; + } + setSections(newSections); + }; + + // Eliminar un elemento de una sección. + const removeElementFromSection = (sectionIndex, elementIndex) => { + const newSections = [...sections]; + newSections[sectionIndex].elements = newSections[sectionIndex].elements.filter((_, i) => i !== elementIndex); + setSections(newSections); + }; + + // Agregar una opción a un elemento + const addOptionToElement = (sectionIndex, elementIndex) => { + const newOption = { name: '', orden: 0 }; + const newSections = [...sections]; + newSections[sectionIndex].elements[elementIndex].options.push(newOption); + setSections(newSections); + }; + + + // actualizar una opción + const updateOption = (sectionIndex, elementIndex, optionIndex, key, value) => { + const newSections = [...sections]; + if (key === 'orden') { + newSections[sectionIndex].elements[elementIndex].options[optionIndex][key] = Number(value); // Convertir a número + } else { + newSections[sectionIndex].elements[elementIndex].options[optionIndex][key] = value; + } + setSections(newSections); + }; + + // Elminar una opción de un elemento + const removeOptionFromElement = (sectionIndex, elementIndex, optionIndex) => { + const newSections = [...sections]; + newSections[sectionIndex].elements[elementIndex].options = newSections[sectionIndex].elements[elementIndex].options.filter((_, i) => i !== optionIndex); + setSections(newSections); + }; + + + // manejar el orden de las peticiones creando un flujo correcto. + const handleSubmit = async (e) => { + e.preventDefault(); + + try{ + // 1. Creamos el fomrulario dinámico + const formResponse = await api.dynamicForms.dynamicForms.post({nombre: formName}); + const FormId = formResponse.data.data.id; // Verifica que el ID se obtenga correctamente + + // 2. Creamos las secciones del formulario + const sectionPromises = sections.map(section => + api.dynamicForms.secciones.post({nombre: section.name}) + ); + const sectionResponses = await Promise.all(sectionPromises); + const sectionIds = sectionResponses.map(res => res.data.data.id); + + // 3. Asociamos las secciones con el formulario creado + const formSectionsPromises = sectionIds.map((sectionId, index) => + api.dynamicForms.dynamicForms.postFormSection(FormId, sectionId, {orden: sections[index].orden,}) + ); + await Promise.all(formSectionsPromises); + + // 4. Creamos los elementos y los asociamos a las secciones + for (const [sectionIndex, section] of sections.entries()){ + const elementPromises = section.elements.map(element => + api.dynamicForms.elementos.post({nombre: element.name, tipo: element.type, formato: element.formato}) + ); + const elementosResponses = await Promise.all(elementPromises); + const elementIds = elementosResponses.map(res => res.data.data.id); + + const elementAssociationPromises = elementIds.map((elementId, elementIndex) => + api.dynamicForms.secciones.postSectionElement(sectionIds[sectionIndex], elementId, {orden: section.elements[elementIndex].orden,}) + ) + await Promise.all(elementAssociationPromises); + + // 5. Creamos las opciones para los elementos que lo requieran + for (const [elementIndex, element] of section.elements.entries()) { + if (element.type === 'opcion_multiple' || element.type === 'desplegable' || element.type === 'casillas') { + const optionPromises = element.options.map(option => + api.dynamicForms.opciones.post({ nombre: option }) + ); + const optionResponses = await Promise.all(optionPromises); + const optionIds = optionResponses.map(res => res.data.data.id); + + const elementOptionPromises = optionIds.map((optionId, optionIndex) => + api.dynamicForms.elementos.postElementOption(elementIds[elementIndex], optionId, {orden: element.options[optionIndex].orden,}) + ); + await Promise.all(elementOptionPromises); + } + } + } + // 6. Creamos la modalidad y le asociamos el form + const formData = new FormData(); + formData.append('nombre', nombre); + formData.append('descripcion', descripcion) + formData.append('monto_maximo', montoMaximo); + formData.append('mostrar', mostrar); + formData.append('archivado', archivado); + if (imagen){ + formData.append('imagen', imagen); + } + formData.append('dynamic_form', FormId); + await api.modalidades.post(formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + alert('Modalidad creada con éxito'); + } catch (error){ + console.error('Error al crear la modalidad.', error); + alert('Ocurrió un error al crear la modalidad.') + } + }; + return( + +
+ + {/* Card blanco para el formulario */} +
+
+
+ + setNombre(e.target.value)} + placeholder="Ingrese el nombre" + required + /> +
+
+ +