diff --git a/cosiap_api/dynamic_forms/migrations/0007_RFecha_charfield.py b/cosiap_api/dynamic_forms/migrations/0007_RFecha_charfield.py new file mode 100644 index 0000000000000000000000000000000000000000..b6bdb95d7d10580afc94baaf1cc5efc97f05fa2a --- /dev/null +++ b/cosiap_api/dynamic_forms/migrations/0007_RFecha_charfield.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.8 on 2024-10-10 00:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dynamic_forms', '0006_valor_respuestas_string'), + ] + + operations = [ + migrations.AlterField( + model_name='rfecha', + name='valor', + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/cosiap_api/dynamic_forms/models.py b/cosiap_api/dynamic_forms/models.py index 799578213be70bb066bd5efc31e610668b35d6d5..d1f07829e2c4a824040c5ec14eeee62501a06c6b 100644 --- a/cosiap_api/dynamic_forms/models.py +++ b/cosiap_api/dynamic_forms/models.py @@ -310,7 +310,8 @@ class RHora(Respuesta): class RFecha(Respuesta): - valor = models.DateField(null=True, blank=True) + valor = models.CharField(max_length=255, null=True, blank=True) + def getStringValue(self): if self.valor is None: @@ -399,22 +400,7 @@ class RDesplegable(Respuesta): return str(self.valor) class Meta: verbose_name_plural = '17. R Desplegables' - - def clean(self): - super().clean() - respuesta = self.valor - obligatorio = self.elemento.obligatorio - opcOtro = self.elemento.opcionOtro - otro = self.otro - if (not respuesta) and obligatorio: - raise ValidationError("Este campo es Obligatorio.") - if obligatorio: - if not ((opcOtro and (otro and otro.strip())) or not opcOtro) and (respuesta and respuesta.nombre == 'Otro'): - raise ValidationError("Este campo es obligatorio") - if (otro and otro.strip()) and (respuesta and not respuesta.nombre == 'Otro'): - raise ValidationError("No esta seleccionada opcion Otro") - if respuesta and (respuesta.nombre == 'Otro' and not( otro and otro.strip())): - raise ValidationError("Si eliges 'otro', debes proporcionar más detalles en el campo 'otro'.") + class RDocumento(Respuesta): class Status(models.TextChoices): diff --git a/cosiap_api/dynamic_forms/serializers.py b/cosiap_api/dynamic_forms/serializers.py index f07f268e50fcb9a31bc583185396105a3b93f05d..dec0cd14062f0bd4a7f36c10a6f654a4c9a8eebe 100644 --- a/cosiap_api/dynamic_forms/serializers.py +++ b/cosiap_api/dynamic_forms/serializers.py @@ -263,21 +263,14 @@ class RespuestaFormularioSerializer(DynamicModelSerializer): self.forms_cache = {form.id: form for form in qs} # Prefetch para las respuestas - qs = Respuesta.objects.filter(**respuesta_filter_kwargs).exclude(elemento__tipo=Elemento.Tipo.CASILLAS).select_subclasses().select_related( + qs = Respuesta.objects.filter(**respuesta_filter_kwargs).select_subclasses().select_related( 'elemento', 'registro_seccion', 'registro_seccion__seccion', 'registro_seccion__registro_formulario', f'registro_seccion__registro_formulario__{self.one_to_one_field_name}' ) - qs_casillas = Respuesta.objects.filter(**respuesta_filter_kwargs, elemento__tipo=Elemento.Tipo.CASILLAS).select_subclasses().select_related( - 'elemento', - 'registro_seccion', - 'registro_seccion__seccion', - 'registro_seccion__registro_formulario', - f'registro_seccion__registro_formulario__{self.one_to_one_field_name}' - ).prefetch_related('valor') - qs = list(qs) + list(qs_casillas) + qs = list(qs) rs_cache = {} for respuesta in qs: #(respuesta = INstancia de RNUmerico) diff --git a/cosiap_api/dynamic_tables/DynamicTableDynamicForm.py b/cosiap_api/dynamic_tables/DynamicTableDynamicForm.py index 7007f279697d30ec4d26fff9dddec7b3dc272298..6d74428751c6624d6c638d7b1ce1977969824495 100644 --- a/cosiap_api/dynamic_tables/DynamicTableDynamicForm.py +++ b/cosiap_api/dynamic_tables/DynamicTableDynamicForm.py @@ -18,7 +18,6 @@ class DynamicTableDynamicForm(DynamicTable): solicitud = super().retrieve_instance_data(instance) solicitud_id = solicitud.get('id') solicitud_instance = Solicitud.objects.get(id=solicitud_id) - form = RespuestaFormularioSerializer(solicitud_instance, dynamic_form_source='modalidad__dynamic_form') form_data = form.data diff --git a/cosiap_api/solicitudes/migrations/0006_RFecha_charfield.py b/cosiap_api/solicitudes/migrations/0006_RFecha_charfield.py new file mode 100644 index 0000000000000000000000000000000000000000..eb15118154aea05778f2ec8d61086307d9246ae7 --- /dev/null +++ b/cosiap_api/solicitudes/migrations/0006_RFecha_charfield.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0.8 on 2024-10-10 00:46 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('solicitudes', '0005_verbose_name_dynamicformat_dynamicform'), + ] + + operations = [ + migrations.AlterModelOptions( + name='convenio', + options={'ordering': ['pk'], 'verbose_name': 'Convenio', 'verbose_name_plural': 'Convenios'}, + ), + ] diff --git a/cosiap_api/solicitudes/respuestas_serializer.py b/cosiap_api/solicitudes/respuestas_serializer.py index 0367ffdc00fd6bc5b7be770439e2621cf5f5de34..2ea1f9bb18fc081333b3d537e63b747876b7c6d0 100644 --- a/cosiap_api/solicitudes/respuestas_serializer.py +++ b/cosiap_api/solicitudes/respuestas_serializer.py @@ -72,7 +72,7 @@ class RespuestaSerializer(serializers.Serializer): return isinstance(value, str) if isinstance(respuesta_instance, (RHora, RFecha)): - return isinstance(value, str) # Puedes añadir validaciones de formato aquí si lo deseas + return isinstance(value, str) return True # Si no es uno de los tipos conocidos, se considera válido diff --git a/cosiap_api/solicitudes/views.py b/cosiap_api/solicitudes/views.py index 82e8ef5cdddebf0810fa386e6bd7a4c8481f8fb2..cfe36d2f3fcde9aca8042494221d50c66ec9ceec 100644 --- a/cosiap_api/solicitudes/views.py +++ b/cosiap_api/solicitudes/views.py @@ -92,6 +92,7 @@ class SolicitarAPIView(BasePermissionAPIView): # Procesar las respuestas enviadas respuestas = self._extract_respuestas_from_formdata(request.data) + print(respuestas) for respuesta in respuestas: serializer = RespuestaSerializer(data=respuesta, context={'registro_formulario': registro_formulario}) if serializer.is_valid(raise_exception=True): @@ -107,26 +108,33 @@ class SolicitarAPIView(BasePermissionAPIView): def _extract_respuestas_from_formdata(self, data): """ - Extrae las respuestas en el formato esperado de FormData. + Extrae las respuestas en el formato esperado de FormData, sin asumir que los índices son consecutivos. """ respuestas = [] - index = 0 - while f"respuestas[{index}][seccion_id]" in data: - respuesta = { - 'seccion_id': data.get(f"respuestas[{index}][seccion_id]"), - 'elemento_id': data.get(f"respuestas[{index}][elemento_id]"), - } - - # Agregar valor_texto solo si está presente - if f"respuestas[{index}][valor_texto]" in data: - respuesta['valor_texto'] = data.get(f"respuestas[{index}][valor_texto]") - - # Agregar valor_file solo si está presente - if f"respuestas[{index}][valor_file]" in data: - respuesta['valor_file'] = data.get(f"respuestas[{index}][valor_file]") - - respuestas.append(respuesta) - index += 1 + + # Buscar todas las claves que coinciden con el patrón 'respuestas' en el FormData + for key in data.keys(): + # Extraer el índice del patrón 'respuestas[X][seccion_id]' donde X es el índice + if "respuestas" in key and "seccion_id" in key: + # Extraer el índice usando regex o split + index = key.split('[')[1].split(']')[0] + + # Crear la respuesta correspondiente + respuesta = { + 'seccion_id': data.get(f"respuestas[{index}][seccion_id]"), + 'elemento_id': data.get(f"respuestas[{index}][elemento_id]"), + } + + # Agregar valor_texto solo si está presente + if f"respuestas[{index}][valor_texto]" in data: + respuesta['valor_texto'] = data.get(f"respuestas[{index}][valor_texto]") + + # Agregar valor_file solo si está presente + if f"respuestas[{index}][valor_file]" in data: + respuesta['valor_file'] = data.get(f"respuestas[{index}][valor_file]") + + respuestas.append(respuesta) + return respuestas @@ -161,6 +169,7 @@ class SolicitarAPIView(BasePermissionAPIView): # Procesar las respuestas enviadas respuestas = self._extract_respuestas_from_formdata(request.data) + print(respuestas) for respuesta in respuestas: serializer = RespuestaSerializer(data=respuesta, context={'registro_formulario': registro_formulario}) diff --git a/cosiap_frontend/src/App.jsx b/cosiap_frontend/src/App.jsx index 93d6d5528f515894eb4427f859b62f8180881aaa..ea4bc2962499ea2574f4cac4b8175f91fa34786d 100644 --- a/cosiap_frontend/src/App.jsx +++ b/cosiap_frontend/src/App.jsx @@ -29,6 +29,8 @@ import EditModalidad from "./components/modalidades/EditarModalidad"; import SolicitarModalidad from "./components/modalidades/Modalidad"; import Perfil from '@/components/users/Perfil/Perfil'; import ListaSolicitudes from "./components/solicitudes/HistorialSolicitudes"; +import EditarSolicitud from "./components/solicitudes/EditarSolicitud"; +import VisualizarSolicitud from "./components/solicitudes/VerSolicitud"; function App() { const [viewPageLoader, setViewPageLoader] = useState(false); @@ -102,6 +104,8 @@ function RoutesApp({ setViewPageLoader }) { } /> } /> } /> + } /> + } /> } /> {/* Solo administradores pueden acceder a estas url */} ax.post('api/solicitudes/solicitar/', data), - update: (id,data) => ax.put(`api/solicitudes/solicitar/${id}`, data) + update: (id,data) => ax.put(`api/solicitudes/solicitar/${id}/`, data) } }, dynamicTables: { diff --git a/cosiap_frontend/src/components/common/utility/RenderElementEdit.jsx b/cosiap_frontend/src/components/common/utility/RenderElementEdit.jsx new file mode 100644 index 0000000000000000000000000000000000000000..a3602e5bcb8e2ecf93943de6747a5943886006a2 --- /dev/null +++ b/cosiap_frontend/src/components/common/utility/RenderElementEdit.jsx @@ -0,0 +1,203 @@ +import '@/components/modalidades/Modalidad.css' + +export const renderElemento = (seccionId, elemento, handleInputChange, handleCheckboxChange, respuestas) => { + const { tipo, opciones, id, nombre, obligatorio } = elemento; + + // Buscar si ya existe una respuesta para este elemento + const respuestaElemento = respuestas.find( + (r) => r.seccion_id === seccionId && r.elemento_id === id + ); + + // Determinar el valor prellenado + const valorPrellenado = respuestaElemento ? respuestaElemento.valor : ""; + const status = respuestaElemento ? respuestaElemento.status : ""; + const observacion = respuestaElemento ? respuestaElemento.observacion : ""; + + console.log(status, observacion) + + // Determinamos el borde del color del card basado en el status + let borderColorClass = ""; + let estado = ""; + switch (status) { + case "valido": + borderColorClass = "border-green"; + estado = "Aprobado" // borde verde para válido + break; + case "revisando": + borderColorClass = "border-yellow"; // borde amarillo para revisando + estado = "En revisión" + break; + case "invalido": + borderColorClass = "border-red"; // borde rojo para inválido + estado = "Documento Incorrecto" + break; + default: + borderColorClass = ""; // sin borde especial si no hay estado + } + + const handleChange = (e) => { + const valor = e.target.value; + console.log(`Valor seleccionado: ${valor}`); + handleInputChange(seccionId, id, valor); + }; + + const renderInput = (inputElement, obligatorio) => ( +
+ {inputElement} + {obligatorio && *obligatorio} +

+ {status && ( + + Estatus: {estado} + + )} +

+ {observacion && ( + + Observación: {observacion} + + )} +
+ ); + + switch (tipo) { + case "texto_corto": + return renderInput( + , + obligatorio + ); + case "texto_parrafo": + return renderInput( +