From 9845202556f42f91844434015bb453a7f98ff218 Mon Sep 17 00:00:00 2001 From: Elliot Axel Noriega Date: Wed, 23 Oct 2024 13:43:58 -0600 Subject: [PATCH 1/4] Arreglo de funcionalidad de datos bancarios --- .../users/Perfil/Label_InputFile.jsx | 1 - .../users/RecepcionApoyo/DatosBancarios.jsx | 19 ++++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cosiap_frontend/src/components/users/Perfil/Label_InputFile.jsx b/cosiap_frontend/src/components/users/Perfil/Label_InputFile.jsx index 585b0b5..7ed66cf 100644 --- a/cosiap_frontend/src/components/users/Perfil/Label_InputFile.jsx +++ b/cosiap_frontend/src/components/users/Perfil/Label_InputFile.jsx @@ -42,7 +42,6 @@ export default function Label_InputFile({urlFile, label, id, name, type, placeho isDisabled={isDisabled} errors={errors} /> -
{message}
{/* Aquí mostramos el área de vista previa si hay un archivo seleccionado */} {selectedFile && !isDisabled && ( //Cuando hay un archivo seleccionado y esta habilitado el campo diff --git a/cosiap_frontend/src/components/users/RecepcionApoyo/DatosBancarios.jsx b/cosiap_frontend/src/components/users/RecepcionApoyo/DatosBancarios.jsx index 23795b8..e0af8a7 100644 --- a/cosiap_frontend/src/components/users/RecepcionApoyo/DatosBancarios.jsx +++ b/cosiap_frontend/src/components/users/RecepcionApoyo/DatosBancarios.jsx @@ -48,8 +48,8 @@ export default function DatosBancarios({obtenerInformacionSolicitante, datosSoli nombre_banco: datosSolicitante.datos_bancarios ? (datosSolicitante.datos_bancarios.nombre_banco || "") : "", cuenta_bancaria: datosSolicitante.datos_bancarios ? (datosSolicitante.datos_bancarios.cuenta_bancaria || "") : "", clabe_bancaria: datosSolicitante.datos_bancarios ? (datosSolicitante.datos_bancarios.clabe_bancaria || "") : "", - doc_estado_cuenta: datosSolicitante.datos_bancarios ? (datosSolicitante.datos_bancarios.doc_estado_cuenta || "") : "", - doc_constancia_sat: datosSolicitante.datos_bancarios ? (datosSolicitante.datos_bancarios.doc_constancia_sat || "") : "", + doc_estado_cuenta: "", + doc_constancia_sat: "", codigo_postal_fiscal: datosSolicitante.datos_bancarios ? (datosSolicitante.datos_bancarios.codigo_postal_fiscal || "") : "", regimen: datosSolicitante.datos_bancarios ? (datosSolicitante.datos_bancarios.regimen || "") : "", }); @@ -63,9 +63,6 @@ export default function DatosBancarios({obtenerInformacionSolicitante, datosSoli // Función que maneja el envío del formulario const handleFormSubmission = async (data) => { - if (!isEditing) { - return; // Si no está en modo edición, no se envía el formulario - } setViewPageLoader(true); // Muestra un loader mientras se realiza la petición try { @@ -77,8 +74,13 @@ export default function DatosBancarios({obtenerInformacionSolicitante, datosSoli formData.append('nombre_banco', data.nombre_banco) formData.append('cuenta_bancaria', data.cuenta_bancaria) formData.append('clabe_bancaria', data.clabe_bancaria) - formData.append('doc_estado_cuenta', data.doc_estado_cuenta[0]) - formData.append('doc_constancia_sat', data.doc_constancia_sat[0]) + if (data.doc_estado_cuenta){ //Si el input del estado de cuenta no es nulo, modificaron el campo + formData.append('doc_estado_cuenta', data.doc_estado_cuenta[0]); + } + if (data.doc_constancia_sat){ //Si el input de la constancia SAT no es nulo + formData.append('doc_constancia_sat', data.doc_constancia_sat[0]); + } + formData.append('codigo_postal_fiscal', data.codigo_postal_fiscal) formData.append('regimen', data.regimen) @@ -130,6 +132,7 @@ export default function DatosBancarios({obtenerInformacionSolicitante, datosSoli // Función para manejar el clic de "Guardar" con validación const handleSaveClick = (data) => { + console.log("Data",data) if (Object.values(modifiedFields).some((isModified) => isModified)) { setShowModalConfirmation(true); // Mostrar el modal de confirmación si no hay errores } @@ -235,7 +238,6 @@ export default function DatosBancarios({obtenerInformacionSolicitante, datosSoli isDisabled={!isEditing} errors={errors.doc_estado_cuenta ? errors.doc_estado_cuenta.message : undefined} onChange={handleInputChange} - message="Solamente archivos PDF" />
@@ -250,7 +252,6 @@ export default function DatosBancarios({obtenerInformacionSolicitante, datosSoli isDisabled={!isEditing} errors={errors.doc_constancia_sat ? errors.doc_constancia_sat.message : undefined} onChange={handleInputChange} - message="Solamente archivos PDF" />
-- GitLab From 2e20fec34b7e139c21f1071a970fca9888b84fe0 Mon Sep 17 00:00:00 2001 From: Elliot Axel Noriega Date: Wed, 23 Oct 2024 14:39:55 -0600 Subject: [PATCH 2/4] =?UTF-8?q?Arreglo=20de=20funcionalidad=20de=20informa?= =?UTF-8?q?cion=20de=20identificaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../users/Perfil/InformacionIdentificacion.jsx | 9 +-------- cosiap_frontend/src/components/users/Perfil/Perfil.jsx | 4 ++-- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/cosiap_frontend/src/components/users/Perfil/InformacionIdentificacion.jsx b/cosiap_frontend/src/components/users/Perfil/InformacionIdentificacion.jsx index 194a402..da329cb 100644 --- a/cosiap_frontend/src/components/users/Perfil/InformacionIdentificacion.jsx +++ b/cosiap_frontend/src/components/users/Perfil/InformacionIdentificacion.jsx @@ -21,11 +21,6 @@ export default function InformacionIdentificacion( {datosSolicitante, setViewPag formState: { errors, isSubmitting } // Estado del formulario: errores y si se está enviando } = useForm({ resolver: yupResolver(IdentificationValidationSchema), // Resolver para la validación con Yup - defaultValues: { - RFC: "", - curp: "", - INE: "", - } }); // Función para resetear los datos del formulario con los valores actuales del solicitante @@ -68,13 +63,11 @@ export default function InformacionIdentificacion( {datosSolicitante, setViewPag }, }); console.log(response) + // Cerrar modal de confirmación y deshabilitar la edición setShowModalConfirmation(false); setIsEditing(false); setModifiedFields({}); // Vaciar los campos modificados - if(datosSolicitante.INE){ - setUrlFileIne(apiUrl + response.data.solicitante.INE);//Declaramos la ruta con el archivo subido - } setShowAlertSuccesful(true); } catch (error) { console.error(error); // Manejar errores diff --git a/cosiap_frontend/src/components/users/Perfil/Perfil.jsx b/cosiap_frontend/src/components/users/Perfil/Perfil.jsx index 4b80460..6d16353 100644 --- a/cosiap_frontend/src/components/users/Perfil/Perfil.jsx +++ b/cosiap_frontend/src/components/users/Perfil/Perfil.jsx @@ -26,8 +26,8 @@ export default function Perfil( {setViewPageLoader} ){ const obtenerInformacionSolicitante = async () =>{ if (uid){ - setViewPageLoader(true); try { + setViewPageLoader(true); //Tratamos de obtener el solicitante con el id const responseObtain = await api.usuarios.solicitantes.getById(uid); //Declaramos los datos del solicitante @@ -68,7 +68,7 @@ export default function Perfil( {setViewPageLoader} ){ setShowAlertSuccesful={setShowAlertSuccesful} /> -- GitLab From 23a7e2b785f6bb106b8c0426ce248ef2677e2f82 Mon Sep 17 00:00:00 2001 From: Elliot Axel Noriega Date: Wed, 23 Oct 2024 20:09:49 -0600 Subject: [PATCH 3/4] Reacomodo de datos bancarios --- .../DatosBancarios.jsx | 0 .../src/components/users/Perfil/Direccion.jsx | 13 ++++++++++--- .../users/Perfil/InformacionIdentificacion.jsx | 6 ++++-- .../users/Perfil/InformacionPersonal.jsx | 5 ++++- .../src/components/users/Perfil/Perfil.jsx | 17 ++++++++++++++--- 5 files changed, 32 insertions(+), 9 deletions(-) rename cosiap_frontend/src/components/users/{RecepcionApoyo => Perfil}/DatosBancarios.jsx (100%) diff --git a/cosiap_frontend/src/components/users/RecepcionApoyo/DatosBancarios.jsx b/cosiap_frontend/src/components/users/Perfil/DatosBancarios.jsx similarity index 100% rename from cosiap_frontend/src/components/users/RecepcionApoyo/DatosBancarios.jsx rename to cosiap_frontend/src/components/users/Perfil/DatosBancarios.jsx diff --git a/cosiap_frontend/src/components/users/Perfil/Direccion.jsx b/cosiap_frontend/src/components/users/Perfil/Direccion.jsx index 35fa7e1..21ccef7 100644 --- a/cosiap_frontend/src/components/users/Perfil/Direccion.jsx +++ b/cosiap_frontend/src/components/users/Perfil/Direccion.jsx @@ -9,7 +9,7 @@ import { DirectionInformationValidationSchema } from "@/components/FormsValidati import api from "@/api"; -export default function Direccion({ datosSolicitante, setViewPageLoader, setShowAlertSuccesful }){ +export default function Direccion({ datosSolicitante, obtenerInformacionSolicitante, setViewPageLoader, setShowAlertSuccesful }){ // Estados const [isEditing, setIsEditing] = useState(false); // Estado para controlar si está en modo edición const [modifiedFields, setModifiedFields] = useState({}); // Campos que han sido modificados @@ -38,8 +38,8 @@ export default function Direccion({ datosSolicitante, setViewPageLoader, setShow // Función para resetear los datos del formulario con los valores actuales del solicitante const resetData = () => reset({ direccion: datosSolicitante.direccion || "", - estado: estadoSeleccionado || "", - municipio: municipioSeleccionado || "", + estado: (datosSolicitante.municipio ? (datosSolicitante.municipio.estado.id) : ""), + municipio: (datosSolicitante.municipio ? (datosSolicitante.municipio.id) : ""), codigo_postal: datosSolicitante.codigo_postal || "", poblacion: datosSolicitante.poblacion || "" }); @@ -98,6 +98,9 @@ export default function Direccion({ datosSolicitante, setViewPageLoader, setShow try { // Actualizar los datos del solicitante const response = await api.usuarios.solicitantes.update(datosSolicitante.id, data); + + //Obtenemos de nuevo los datos del solicitante + obtenerInformacionSolicitante(); // Cerrar modal de confirmación y deshabilitar la edición setShowModalConfirmation(false); setIsEditing(false); @@ -133,6 +136,10 @@ export default function Direccion({ datosSolicitante, setViewPageLoader, setShow setIsEditing(false); // Deshabilitar el modo edición resetData(); // Restablecer los datos originales setModifiedFields({}); // Limpiar los campos modificados + if (datosSolicitante.municipio){ + setEstadoSeleccionado(datosSolicitante.municipio.estado.id); + setMunicipioSeleccionado(datosSolicitante.municipio.id); + } }; // Función para manejar el clic de "Guardar" con validación diff --git a/cosiap_frontend/src/components/users/Perfil/InformacionIdentificacion.jsx b/cosiap_frontend/src/components/users/Perfil/InformacionIdentificacion.jsx index da329cb..2c7965d 100644 --- a/cosiap_frontend/src/components/users/Perfil/InformacionIdentificacion.jsx +++ b/cosiap_frontend/src/components/users/Perfil/InformacionIdentificacion.jsx @@ -8,7 +8,7 @@ import { IdentificationValidationSchema } from "@/components/FormsValidations"; import api, { apiUrl } from "@/api"; import Label_InputFile from "./Label_InputFile"; -export default function InformacionIdentificacion( {datosSolicitante, setViewPageLoader, setShowAlertSuccesful} ){ +export default function InformacionIdentificacion( {datosSolicitante, setDatosSolicitante, setViewPageLoader, setShowAlertSuccesful} ){ // Estados const [isEditing, setIsEditing] = useState(false); // Estado para controlar si está en modo edición const [modifiedFields, setModifiedFields] = useState({}); // Campos que han sido modificados @@ -62,7 +62,9 @@ export default function InformacionIdentificacion( {datosSolicitante, setViewPag 'Content-Type': 'multipart/form-data', }, }); - console.log(response) + + //Obtenemos de nuevo los datos del solicitante + obtenerInformacionSolicitante(); // Cerrar modal de confirmación y deshabilitar la edición setShowModalConfirmation(false); diff --git a/cosiap_frontend/src/components/users/Perfil/InformacionPersonal.jsx b/cosiap_frontend/src/components/users/Perfil/InformacionPersonal.jsx index 5277840..836400b 100644 --- a/cosiap_frontend/src/components/users/Perfil/InformacionPersonal.jsx +++ b/cosiap_frontend/src/components/users/Perfil/InformacionPersonal.jsx @@ -8,7 +8,7 @@ import { useEffect, useState } from "react"; import { PersonalInformationValidationSchema } from "@/components/FormsValidations"; import api from "@/api"; -export default function InformacionPersonal({ datosSolicitante, setViewPageLoader, setShowAlertSuccesful }) { +export default function InformacionPersonal({ datosSolicitante, obtenerInformacionSolicitante, setViewPageLoader, setShowAlertSuccesful }) { // Estados const [isEditing, setIsEditing] = useState(false); // Estado para controlar si está en modo edición const [modifiedFields, setModifiedFields] = useState({}); // Campos que han sido modificados @@ -57,6 +57,9 @@ export default function InformacionPersonal({ datosSolicitante, setViewPageLoade // Actualizar los datos del solicitante const response = await api.usuarios.solicitantes.update(datosSolicitante.id, data); console.log(response); + + //Obtenemos de nuevo los datos del solicitante + obtenerInformacionSolicitante(); // Cerrar modal de confirmación y deshabilitar la edición setShowModalConfirmation(false); diff --git a/cosiap_frontend/src/components/users/Perfil/Perfil.jsx b/cosiap_frontend/src/components/users/Perfil/Perfil.jsx index 6d16353..a9956e4 100644 --- a/cosiap_frontend/src/components/users/Perfil/Perfil.jsx +++ b/cosiap_frontend/src/components/users/Perfil/Perfil.jsx @@ -5,6 +5,7 @@ import Alert from "@/components/common/ui/Alert"; import api from "@/api"; import { useAutenticacion } from "@/components/common/utility/Autenticador"; import { useEffect, useState } from "react"; +import DatosBancarios from "./DatosBancarios"; export default function Perfil( {setViewPageLoader} ){ const { uid } = useAutenticacion(); @@ -31,6 +32,7 @@ export default function Perfil( {setViewPageLoader} ){ //Tratamos de obtener el solicitante con el id const responseObtain = await api.usuarios.solicitantes.getById(uid); //Declaramos los datos del solicitante + console.log(responseObtain.data) setDatosSolicitante(responseObtain.data); console.log(responseObtain.data) } catch (error) { // Si da error significa que es su primera vez ingresando @@ -55,20 +57,29 @@ export default function Perfil( {setViewPageLoader} ){ />
-
-
+
+
+ -- GitLab From 5096835d430f5070a5c205d71e7f0b1dd7ac9f3b Mon Sep 17 00:00:00 2001 From: Elliot Axel Noriega Date: Wed, 23 Oct 2024 20:10:34 -0600 Subject: [PATCH 4/4] Funcionalidad de filtros completada --- cosiap_api/dynamic_tables/DynamicTable.py | 6 +- cosiap_frontend/package-lock.json | 11 + cosiap_frontend/package.json | 3 +- cosiap_frontend/src/App.jsx | 2 - .../src/components/FormsValidations.jsx | 36 +- .../SolicitudesAdmin/MenuColumnas.jsx | 2 +- .../SolicitudesAdmin/MenuFiltros.jsx | 458 +++++++++++++++++- .../SolicitudesAdmin/Solicitudes.jsx | 278 ++++++++++- .../common/layouts/LayoutBaseNavigation.jsx | 6 - cosiap_frontend/tailwind.config.js | 15 +- 10 files changed, 770 insertions(+), 47 deletions(-) diff --git a/cosiap_api/dynamic_tables/DynamicTable.py b/cosiap_api/dynamic_tables/DynamicTable.py index ca33432..f772160 100644 --- a/cosiap_api/dynamic_tables/DynamicTable.py +++ b/cosiap_api/dynamic_tables/DynamicTable.py @@ -24,7 +24,7 @@ from zipfile import ZipFile import io from dynamic_forms.models import RDocumento, RegistroSeccion -exclude_pattern = re.compile(r'archivo|mostrar|archivado|INE|doc|id|imagen|staff|user|active|timestamp|^password$|^last_login$|^created_at$|^updated_at$|^usuario_ptr$|^groups$|^user_permissions$|^dynamic_form__nombre$|^dynamic_form__secciones$',re.IGNORECASE) +exclude_pattern = re.compile(r'archivo|mostrar|archivado|INE|doc|id|imagen|staff|user|active|^password$|^last_login$|^created_at$|^updated_at$|^usuario_ptr$|^groups$|^user_permissions$|^dynamic_form__nombre$|^dynamic_form__secciones$',re.IGNORECASE) class Reporte(serializers.ModelSerializer): @@ -230,10 +230,10 @@ class DynamicTable(serializers.ModelSerializer): filter_info['choices'] = [{'label': choice[1], 'value': choice[0]} for choice in field.choices] elif isinstance(field, models.IntegerField) or isinstance(field, models.FloatField) or isinstance(field, models.DecimalField): filter_info['html_type'] = 'numberInput' - filter_info['lookups'] = {'gt': None, 'lt': None} + filter_info['lookups'] = {'gte': None, 'lte': None} elif isinstance(field, models.DateField) or isinstance(field, models.DateTimeField): filter_info['html_type'] = 'dateInput' - filter_info['lookups'] = {'gt': None, 'lt': None, 'gte': None, 'lte': None} + filter_info['lookups'] = {'gte': None, 'lte': None} elif isinstance(field, models.BooleanField): filter_info['html_type'] = 'checkbox' filter_info['lookups'] = {'iexact': None} diff --git a/cosiap_frontend/package-lock.json b/cosiap_frontend/package-lock.json index 4aad360..2a29aeb 100644 --- a/cosiap_frontend/package-lock.json +++ b/cosiap_frontend/package-lock.json @@ -19,6 +19,7 @@ "react-dom": "^18.2.0", "react-helmet-async": "^2.0.5", "react-hook-form": "^7.52.1", + "react-range": "^1.8.14", "react-router-dom": "^6.23.1", "yup": "^1.4.0" }, @@ -4038,6 +4039,16 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-range": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/react-range/-/react-range-1.10.0.tgz", + "integrity": "sha512-kDo0LiBUHIQIP8menx0UoxTnHr7UXBYpIYl/DR9jCaO1o29VwvCLpkP/qOTNQz5hkJadPg1uEM07XJcJ1XGoKw==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, "node_modules/react-redux": { "version": "7.2.9", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", diff --git a/cosiap_frontend/package.json b/cosiap_frontend/package.json index 6ed0a1e..f038929 100644 --- a/cosiap_frontend/package.json +++ b/cosiap_frontend/package.json @@ -23,7 +23,8 @@ "react-hook-form": "^7.52.1", "react-router-dom": "^6.23.1", "yup": "^1.4.0", - "react-beautiful-dnd": "^13.1.0" + "react-beautiful-dnd": "^13.1.0", + "react-range": "^1.8.14" }, "devDependencies": { "@types/react": "^18.2.66", diff --git a/cosiap_frontend/src/App.jsx b/cosiap_frontend/src/App.jsx index 7e88558..7ea95e0 100644 --- a/cosiap_frontend/src/App.jsx +++ b/cosiap_frontend/src/App.jsx @@ -32,7 +32,6 @@ 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"; -import RecepcionApoyo from "./components/users/RecepcionApoyo/RecepcionApoyo"; import Solicitudes from "./components/SolicitudesAdmin/Solicitudes"; import ListaUsuarios from "./components/admin/TablaUsuarios"; import ListaSolicitudesSolicitante from "./components/admin/HistorialAdmin"; @@ -110,7 +109,6 @@ function RoutesApp({ setViewPageLoader }) { }> } /> } /> - } /> } /> } /> } /> diff --git a/cosiap_frontend/src/components/FormsValidations.jsx b/cosiap_frontend/src/components/FormsValidations.jsx index 934eaae..20bfada 100644 --- a/cosiap_frontend/src/components/FormsValidations.jsx +++ b/cosiap_frontend/src/components/FormsValidations.jsx @@ -109,13 +109,13 @@ export const IdentificationValidationSchema = Yup.object().shape({ RFC: RFC_VALIDATION, curp: CURP_VALIDATION, INE: Yup.mixed() - .test("required", "El archivo INE es requerido", (value) => value && value.length > 0) // Validar si el archivo está vacío - .test("fileFormat", "Formato de archivo no soportado", (value) => { - return value && value[0] && ["application/pdf"].includes(value[0].type); // Asegúrate de validar solo si hay un archivo - }) - .test("fileSize", "El archivo supera el tamaño máximo", (value) => { - return value && value[0] && value[0].size <= FILE_SIZE; - }) + .nullable() // Permite que sea un valor nulo o vacío + .notRequired() // No es obligatorio + .test( + 'fileFormat', + 'Solo se permiten archivos PDF', + (value) => !value || (value[0] && value[0].type === 'application/pdf') // Valida solo si hay archivo + ), }); export const DatosBancariosValidationSchema = Yup.object().shape({ @@ -132,16 +132,22 @@ export const DatosBancariosValidationSchema = Yup.object().shape({ .matches(/^\d{16}$/, 'La CLABE bancaria debe tener 16 dígitos'), doc_estado_cuenta: Yup.mixed() - .nullable() - .test('fileType', 'Solo se permiten archivos PDF', value => { - return !value || (value && value[0] && value[0].type === 'application/pdf'); - }), + .nullable() // Permite que sea un valor nulo o vacío + .notRequired() // No es obligatorio + .test( + 'fileFormat', + 'Solo se permiten archivos PDF', + (value) => !value || (value[0] && value[0].type === 'application/pdf') // Valida solo si hay archivo + ), doc_constancia_sat: Yup.mixed() - .nullable() - .test('fileType', 'Solo se permiten archivos PDF', value => { - return !value || (value && value[0] && value[0].type === 'application/pdf'); - }), + .nullable() // Permite que sea un valor nulo o vacío + .notRequired() // No es obligatorio + .test( + 'fileFormat', + 'Solo se permiten archivos PDF', + (value) => !value || (value[0] && value[0].type === 'application/pdf') // Valida solo si hay archivo + ), codigo_postal_fiscal: Yup.string() .required('El código postal fiscal es obligatorio') diff --git a/cosiap_frontend/src/components/SolicitudesAdmin/MenuColumnas.jsx b/cosiap_frontend/src/components/SolicitudesAdmin/MenuColumnas.jsx index 43d5dad..5bfb386 100644 --- a/cosiap_frontend/src/components/SolicitudesAdmin/MenuColumnas.jsx +++ b/cosiap_frontend/src/components/SolicitudesAdmin/MenuColumnas.jsx @@ -56,7 +56,7 @@ export default function MenuColumnas( {columnas, columnasOcultas, setColumnasOcu
- {key === 'modalidad__nombre' ? "Modalidad" : key === 'solicitante__nombre' ? "Solicitante" : value} + {key === 'modalidad__nombre' ? "Modalidad" : key === 'solicitante__nombre' ? "Solicitante" : key === 'timestamp' ? "Fecha" : value}
))} diff --git a/cosiap_frontend/src/components/SolicitudesAdmin/MenuFiltros.jsx b/cosiap_frontend/src/components/SolicitudesAdmin/MenuFiltros.jsx index 66c832d..cc0daef 100644 --- a/cosiap_frontend/src/components/SolicitudesAdmin/MenuFiltros.jsx +++ b/cosiap_frontend/src/components/SolicitudesAdmin/MenuFiltros.jsx @@ -1,18 +1,462 @@ -export default function MenuFiltros( {filtros, setFiltros} ){ +import { useEffect, useState } from "react"; +import { Range } from 'react-range'; + +export default function MenuFiltros({ filtros, setFiltros, columnasOcultas, menuRef, solicitudes }) { + const [mostrarSubmenu, setMostrarSubmenu] = useState(null); // Se guarda el nombre de la columna activa. + const [submenuData, setSubmenuData] = useState([]); // Datos específicos del submenu. + + const handleArrowClick = (filtro) => { + if (mostrarSubmenu === filtro.campo) { + setMostrarSubmenu(null); // Si ya está abierto, lo cerramos. + } else { + setMostrarSubmenu(filtro.campo); // Abrimos el submenu correspondiente. + setSubmenuData(filtro); // Guardamos la información del filtro activo. + } + }; + + //Funcion que elimina todos los filtros + const handleDeleteAllFilters = () => { + //Cierro el submenu si se encuentra abierto + setMostrarSubmenu(null); + // Mapea todos los filtros y resetea sus valores según el tipo de filtro + const resetedFilters = filtros.map((filtro) => { + if (hasFilter(filtro)) { + if (filtro.html_type === "numberInput" || filtro.html_type === "dateInput") { + // Resetea filtros numéricos y de fecha + return { ...filtro, lookups: { gte: null, lte: null } }; + } else if (filtro.html_type === "textInput") { + if (filtro.lookups.icontains){ + //Reseteo filtros de cadenas de texto + return { ...filtro, lookups: { icontains: null } }; + } + if (filtro.lookups.iexact) { + //Reseteo filtros de choices + return { ...filtro, lookups: { iexact: null } }; + } + // Resetea filtros de texto + return { ...filtro, lookups: { icontains: null, iexact: null } }; + } + } + return filtro; // Si no tiene filtro, lo retorna sin modificar + }); + + // Actualiza el estado de todos los filtros con los valores reseteados + setFiltros(resetedFilters); + }; + + //Funcion para comprobar si una columna tiene filtros + const hasFilter = (filtro) => { + if ((filtro.html_type === "numberInput") || (filtro.html_type === "dateInput")) { + if ((filtro.lookups.gte === null) && (filtro.lookups.lte === null)){ + return false; + }else{ + return true; + } + } else if (filtro.html_type === "textInput") { + if ((filtro.lookups.icontains === null) || (filtro.lookups.iexact === null)){ + return false; + }else{ + return true; + } + } + } + + const updateFilterValue = (campo, newLookups) => { + const updatedFiltros = filtros.map((filter) => { + if (filter.campo === campo) { + return { ...filter, lookups: newLookups }; // Actualiza únicamente los lookups. + } + return filter; + }); + console.log('Filtros actualizados: ',updatedFiltros) + setFiltros(updatedFiltros); + }; + return ( <> -
-
+
+
-
- +
+ {filtros.map((filtro, key) => ( + !columnasOcultas.includes(filtro.campo) && ( +
handleArrowClick(filtro)} // Pasamos el filtro completo. + > +
+ + {mostrarSubmenu === filtro.campo ? "arrow_forward_ios" : "arrow_back_ios"} + +
+
+ + {filtro.campo === 'modalidad__nombre' ? "Modalidad" : filtro.campo === 'solicitante__nombre' ? "Solicitante" : filtro.campo === 'timestamp' ? "Fecha" : filtro.label} + +
+ { + hasFilter(filtro) ? ( +
+ + target + +
+ ) : ( +
+ +
+ ) + } + +
+ ) + ))}
+ {/* Submenú que aparece cuando se da click en la flecha */} + {mostrarSubmenu !== null && ( + + )}
); -} \ No newline at end of file +} + +function SubMenuFiltros({ filtro, updateFilterValue, solicitudes, }) { + + const handleDeleteFilter = (filtro) => { + + //Borramos sus campos dependiendo del filtro + if (filtro.html_type === "numberInput") { + updateFilterValue(filtro.campo, { gte: null, lte: null }); + } else if ((filtro.html_type === "textInput") && (filtro.lookups.icontains !== undefined)) { + updateFilterValue(filtro.campo, { icontains: null }); + } else if ((filtro.html_type === "textInput") && (filtro.lookups.iexact !== undefined)) { + updateFilterValue(filtro.campo, { iexact: null }); + } else if (filtro.html_type === "dateInput") { + updateFilterValue(filtro.campo, { gte: null, lte: null }); + } + }; + + return ( +
+
+
+
+ {filtro.campo === 'modalidad__nombre' ? "Modalidad" : filtro.campo === 'solicitante__nombre' ? "Solicitante" : filtro.campo === 'timestamp' ? "Fecha" : filtro.label} +
+
+ {filtro.html_type === "numberInput" && ( + + )} + {filtro.html_type === "textInput" && filtro.lookups.icontains !== undefined && ( + + )} + {filtro.html_type === "textInput" && filtro.lookups.iexact !== undefined && ( + + )} + {filtro.html_type === "dateInput" && ( + + )} +
+
+
+
+ ); +} + +const MultiRangeSliderSelection = ({ filtro, updateFilterValue, solicitudes, handleDeleteFilter }) => { + // Obtenemos el valor máximo del campo específico en las solicitudes + const maxValue = Math.max(...solicitudes.map((solicitud) => solicitud[filtro.campo])); + // Obtenemos el valor mínimo del campo específico en las solicitudes + const minValue = Math.min(...solicitudes.map((solicitud) => solicitud[filtro.campo])); + + // Estado para almacenar los valores del rango + const [values, setValues] = useState([(minValue !== maxValue ? minValue : 0), maxValue]); + + // Efecto para actualizar el estado cuando el filtro cambie + useEffect(() => { + setValues([filtro.lookups.gte || (minValue !== maxValue ? minValue : 0 ), filtro.lookups.lte || maxValue]); + }, [filtro, minValue, maxValue]); + + // Función para manejar el cambio de valores en el rango + const handleRangeChange = (newValues) => { + setValues(newValues); // Actualizamos el estado local del slider + // Actualizamos los valores en el filtro + //Si los dos valores son el valor minimo y maximo + if ((minValue === maxValue ? (newValues[0] === 0) : (newValues[0] === minValue)) && newValues[1] === maxValue) { + //Estableceremos valores nulos, ya que se tomara que no se hiso ningun filtro + updateFilterValue(filtro.campo, { gte: null, lte: null }); + } else { + // Actualizamos los valores en el filtro + updateFilterValue(filtro.campo, { gte: newValues[0], lte: newValues[1]}); + } + }; + + // Función para eliminar los filtros y restablecer los valores del rango + const handleDeleteFilterAndValues = (filtro) => { + handleDeleteFilter(filtro); // Elimina el filtro y sus valores + // Restablece los valores del rango a sus valores mínimos y máximos + setValues([(minValue !== maxValue ? minValue : 0), maxValue]); + }; + + return ( + <> +
+
+ Selecciona rango +
+
+ {/* Mostramos los valores seleccionados */} + {values[0]} + {values[1]} +
+
+
+ ( +
+ {children} +
+ )} + renderThumb={({ props }) => ( +
+ )} + /> +
+ + + ); +}; + + +const ChoicesSelection = ({ filtro, updateFilterValue, handleDeleteFilter }) => { + // Estado para almacenar los valores seleccionados. + const [selectedValues, setSelectedValues] = useState(filtro.lookups.iexact || []); + + // Efecto para actualizar el estado cuando el filtro cambie + useEffect(() => { + setSelectedValues(filtro.lookups.iexact || []); + }, [filtro]); + // Función para manejar cambios en los checkboxes. + const handleCheckboxChange = (event) => { + const { value, checked } = event.target; + + // Si el checkbox está marcado, agregamos el valor al arreglo. + if (checked) { + setSelectedValues((prevValues) => [...prevValues, value]); + } else { + // Si está desmarcado, eliminamos el valor del arreglo. + setSelectedValues((prevValues) => + prevValues.filter((v) => v !== value) + ); + } + }; + + // Cuando los valores seleccionados cambien, actualizamos los lookups en el filtro. + useEffect(() => { + updateFilterValue(filtro.campo, { iexact: (selectedValues.length > 0 ? selectedValues : null) }); // Si no hay ninguna opción seleccionada, devolvemos un null + }, [selectedValues]); + + const handleDeleteFilterAndValues = (filtro) => { + handleDeleteFilter(filtro); // Elimina el filtro y sus valores + //Eliminamos los valores actuales + setSelectedValues([]); + } + + return ( + <> +
+ {filtro.choices.map((choice) => ( +
+ + +
+ ))} +
+ + + ); +}; + +const ContainsFilterSelection = ({ filtro, updateFilterValue, handleDeleteFilter }) => { + const [inputValue, setInputValue] = useState(filtro.lookups.icontains || ''); // Estado local para el valor del input. + + // Efecto para actualizar el estado cuando el filtro cambie + useEffect(() => { + setInputValue(filtro.lookups.icontains || ''); + }, [filtro]); + + const handleInputChange = (event) => { + const { value } = event.target; + setInputValue(value); // Actualizamos el valor del input. + // Actualizamos el filtro con el valor ingresado. + //Si la cadena esta vacia + updateFilterValue(filtro.campo, { ...filtro.lookups, icontains: value.trim() === "" || value === " " ? null : value} ); // Si no hay ninguna opción seleccionada, devolvemos un null + }; + + const handleDeleteFilterAndValues = (filtro) => { + //Eliminamos el filtro realizado del arreglo principal + handleDeleteFilter(filtro); + //Reestablecemos los valores + setInputValue(""); + } + + return ( + <> +
+
+ Ingresa el texto a filtrar de este campo +
+
+ +
+ + +
+ + + ); +}; + +const DateSelection = ({ filtro, updateFilterValue, handleDeleteFilter }) => { + const [startDate, setStartDate] = useState(filtro.lookups.gte); // Fecha de inicio + const [endDate, setEndDate] = useState(filtro.lookups.lte); // Fecha de fin + + // Actualizamos los filtros cada vez que cambien las fechas de inicio o fin. + useEffect(() => { + if (startDate !== "" && endDate !== "") { + updateFilterValue(filtro.campo, { gte: startDate, lte: endDate}); + } + }, [startDate, endDate]); + + // Maneja el cambio de la fecha de inicio + const handleStartDateChange = (e) => { + const selectedDate = e.target.value; + // Solo actualiza si la fecha de inicio es menor o igual que la fecha de fin + if (!endDate || selectedDate <= endDate) { + setStartDate(selectedDate); + } + if(selectedDate === ""){ //Si limpia el campo, se borra el estado + setEndDate(""); + } + }; + + const handleDeleteFilterAndValues = (filtro) => { + handleDeleteFilter(filtro); // Elimina el filtro y sus valores + //Eliminamos los valores actuales + setStartDate(""); + setEndDate(""); + } + + // Maneja el cambio de la fecha de fin + const handleEndDateChange = (e) => { + const selectedDate = e.target.value; + // Solo actualiza si la fecha de fin es mayor o igual que la fecha de inicio + if (!startDate || selectedDate >= startDate) { + setEndDate(selectedDate); + } + if(selectedDate === ""){ //Si limpia el campo, se borra el estado + setEndDate(""); + } + }; + + return ( + <> +
+
+ +
+
+ a +
+
+ +
+
+ + + ); +}; diff --git a/cosiap_frontend/src/components/SolicitudesAdmin/Solicitudes.jsx b/cosiap_frontend/src/components/SolicitudesAdmin/Solicitudes.jsx index 166bf99..d0ec8f8 100644 --- a/cosiap_frontend/src/components/SolicitudesAdmin/Solicitudes.jsx +++ b/cosiap_frontend/src/components/SolicitudesAdmin/Solicitudes.jsx @@ -12,6 +12,8 @@ export default function Solicitudes( {setViewPageLoader} ){ const [columnasOcultas, setColumnasOcultas] = useState([]); const [filtros, setFiltros] = useState([]); const [solicitudesOriginales, setSolicitudesOriginales] = useState([]); + const [filtrosAplicados, setFiltrosAplicados] = useState([]); + const [searchTerm, setSearchTerm] = useState(''); //Vistas de columnas y filtros const [viewMenuColumnas, setViewMenuColumnas] = useState(false) @@ -20,9 +22,59 @@ export default function Solicitudes( {setViewPageLoader} ){ //Ref para ventanas y botones de los filtros y columnas const menuColumnasRef = useRef(null); const buttonMenuColumnasRef = useRef(null); + const menuFiltrosRef = useRef(null); + const buttonMenuFiltrosRef = useRef(null); const [sortConfig, setSortConfig] = useState({ key: null, direction: 'ascending' }); + function Regimen (value) { + switch (value) { + case "1": + return "Régimen Simplificado de Confianza" + case "2": + return "Sueldos y salarios e ingresos asimilados a salarios" + case "3": + return "Régimen de Actividades Empresariales y Profesionales" + case "4": + return "Régimen de Incorporación Fiscal" + case "5": + return "Enajenación de bienes" + case "6": + return "Régimen de Actividades Empresariales con ingresos a través de Plataformas Tecnológicas" + case "7": + return "Régimen de Arrendamiento" + case "8": + return "Intereses" + case "9": + return "Obtención de premios" + case "10": + return "Dividendos" + case "11": + return "Demás Ingresos" + case "12": + return "Sin obligaciones fiscales" + default: + return value; + } + } + + // Función para manejar la búsqueda + const handleSearch = (event) => { + const searchTerm = event.target.value.toLowerCase(); // Convertimos a minúsculas para hacer la búsqueda insensible a mayúsculas + setSearchTerm(searchTerm); + + // Filtramos las solicitudes originales según el término de búsqueda + const filteredSolicitudes = solicitudesOriginales.filter(solicitud => { + // Buscamos en todas las columnas. + return Object.values(solicitud).some(value => + value?.toString().toLowerCase().includes(searchTerm) + ); + }); + + setSolicitudes(filteredSolicitudes); + }; + + const obtenerSolicitudes = async () => { try { setViewPageLoader(true) @@ -47,11 +99,16 @@ export default function Solicitudes( {setViewPageLoader} ){ // Efecto para manejar los clics fuera de los menús y notificaciones useEffect(() => { function handleClickOutside(event) { - // Cierra el menú si se hace clic fuera de él y de su botón de toggle + // Cierra el menú de las columnas si se hace clic fuera de él y de su botón de toggle if (menuColumnasRef.current && !menuColumnasRef.current.contains(event.target) && buttonMenuColumnasRef.current && !buttonMenuColumnasRef.current.contains(event.target)){ setViewMenuColumnas(false); } + // Cierra el menú de los filtros si se hace clic fuera de él y de su botón de toggle + if (menuFiltrosRef.current && !menuFiltrosRef.current.contains(event.target) && + buttonMenuFiltrosRef.current && !buttonMenuFiltrosRef.current.contains(event.target)){ + setViewMenuFiltros(false); + } } // Añade el event listener para detectar clics fuera de los elementos @@ -63,6 +120,7 @@ export default function Solicitudes( {setViewPageLoader} ){ }; }, []); + //Cada renderizacion del componente, se obtendran las solicitudes useEffect(() => { obtenerSolicitudes() }, []) @@ -180,12 +238,115 @@ export default function Solicitudes( {setViewPageLoader} ){ })} ); + case 'timestamp': + const fecha = new Date(value); + return ( + + {fecha.toLocaleDateString('es-ES', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + })} + + ); + case 'solicitante__datos_bancarios__regimen': + return Regimen(value) - default: return value; } } + + //Cada que el arreglo de filtros cambie, se aplicaran los filtros + useEffect(() => { + filtrarSolicitudes() + }, [filtros]) + + //Funcion para filtrar las solicitudes en base a los filtros + const filtrarSolicitudes = () => { + // Clonamos las solicitudes originales para no mutarlas + let solicitudesFiltradas = [...solicitudesOriginales]; + let nuevosFiltrosAplicados = []; + + + // Iteramos sobre cada filtro disponible + filtros.forEach(filtro => { + const { campo, lookups, html_type } = filtro; + // console.log(campo, lookups, html_type) + // Filtramos solo si el campo tiene valores en lookups + if (html_type === "numberInput") { + if (lookups.gte !== null && lookups.lte !== null) { + solicitudesFiltradas = solicitudesFiltradas.filter(solicitud => { + // console.log(solicitud,(solicitud[campo] >= lookups.gte), (solicitud[campo] <= lookups.lte)) + return (solicitud[campo] >= lookups.gte) && (solicitud[campo] <= lookups.lte); + }); + // Agregar a la lista de filtros aplicados + nuevosFiltrosAplicados.push({ filtro }); + } + } else if (html_type === "dateInput") { + if (lookups.gte !== null && lookups.lte !== null) { + solicitudesFiltradas = solicitudesFiltradas.filter(solicitud => { + // Convertimos los valores de la solicitud y los filtros a Date + const fechaSolicitud = new Date(solicitud[campo]); + const fechaGte = new Date(lookups.gte); + const fechaLte = new Date(lookups.lte); + + // Truncamos la parte de la hora para gte (solo fecha) y ajustamos lte al final del día + const fechaSolicitudSinHora = new Date(fechaSolicitud.getFullYear(), fechaSolicitud.getMonth(), fechaSolicitud.getDate()); + const fechaGteSinHora = new Date(fechaGte.getFullYear(), fechaGte.getMonth(), fechaGte.getDate()); + + // Límite superior se ajusta para incluir todo el día (hasta las 23:59:59) + const fechaLteFinDia = new Date(fechaLte.getFullYear(), fechaLte.getMonth(), fechaLte.getDate(), 23, 59, 59); + + // Comparación de fechas con gte y lte + return (fechaSolicitudSinHora >= fechaGteSinHora) && (fechaSolicitudSinHora <= fechaLteFinDia); + }); + // Agregar a la lista de filtros aplicados + nuevosFiltrosAplicados.push({ filtro }); + } + } else if (html_type === "textInput") { + if (lookups.icontains) { + solicitudesFiltradas = solicitudesFiltradas.filter(solicitud => + solicitud[campo].toLowerCase().includes(lookups.icontains.toLowerCase()) + ); + // Agregar a la lista de filtros aplicados + nuevosFiltrosAplicados.push({ filtro }); + } + if (lookups.iexact) { + solicitudesFiltradas = solicitudesFiltradas.filter(solicitud => + lookups.iexact.includes(solicitud[campo]) + ); + // Agregar a la lista de filtros aplicados + nuevosFiltrosAplicados.push({ filtro }); + } + } + }); + + setSolicitudes(solicitudesFiltradas); // Actualizamos las solicitudes filtradas + setFiltrosAplicados(nuevosFiltrosAplicados); // Actualizamos los filtros aplicados + }; + + const deleteFilter = (campo) => { + const updatedFiltros = filtros.map((filter) => { + if (filter.campo === campo) { + if ((filter.html_type === "numberInput") || (filter.html_type === "dateInput")) { + //Declaramos gte del filter como nulo igual que lte + filter.lookups.gte = null; + filter.lookups.lte = null; + + } else if (filter.html_type === "textInput") { + if (filter.lookups.icontains){ + filter.lookups.icontains = null; + } + if (filter.lookups.iexact){ + filter.lookups.iexact = null; + } + } + } + return filter; + }); + setFiltros(updatedFiltros); + }; return ( <> @@ -193,7 +354,7 @@ export default function Solicitudes( {setViewPageLoader} ){ )} {viewMenuFiltros && ( - + )}
@@ -204,12 +365,22 @@ export default function Solicitudes( {setViewPageLoader} ){ search - +
+
+ +
+
@@ -218,7 +389,7 @@ export default function Solicitudes( {setViewPageLoader} ){ {(provided) => ( @@ -236,7 +407,7 @@ export default function Solicitudes( {setViewPageLoader} ){ >
- {key === 'modalidad__nombre' ? "Modalidad" : key === 'solicitante__nombre' ? "Solicitante" : value} + {key === 'modalidad__nombre' ? "Modalidad" : key === 'solicitante__nombre' ? "Solicitante" : key === 'timestamp' ? "Fecha" : value} ); +} + +const ShowFilters = ({filtrosAplicados, setFiltrosAplicados, deleteFilter}) => { + function RegimenAbreviado (value) { + switch (value) { + case "1": + return "RSC" + case "2": + return "SSIAS" + case "3": + return "RAEP" + case "4": + return "RIF" + case "5": + return "EB" + case "6": + return "RAEIPT" + case "7": + return "RA" + case "8": + return "I" + case "9": + return "OP" + case "10": + return "D" + case "11": + return "DI" + case "12": + return "SOF" + default: + return value; + } + } + + const texto = (campo, html_type, lookups) => { + if (html_type === "numberInput") { + return `${lookups.gte} - ${lookups.lte}` + } else if (html_type === "dateInput") { + return `${lookups.gte} - ${lookups.lte}` + } else if (html_type === "textInput") { + if (lookups.icontains) { + return lookups.icontains; + } + if (lookups.iexact) { + return ( + // Ponemos los valores que se encuentran dentro de lookups.icontaines en una sola cadena, separados por , + lookups.iexact.map((value, index) => { + return RegimenAbreviado(value) + (index < lookups.iexact.length - 1 ? ", " : "") + }).join("") + ); + } + } + return ""; + } + + // Función que elimina el filtro al hacer clic en el icono "close" + const handleRemoveFilter = (campo, indexToRemove) => { + deleteFilter(campo); + setFiltrosAplicados((prevFiltros) => + prevFiltros.filter((_, index) => index !== indexToRemove) + ); + }; + + return ( + <> + {filtrosAplicados.length > 0 && ( +
+ {filtrosAplicados.map((object, key) => ( +
+
+ + {object.filtro.campo === 'modalidad__nombre' ? "Modalidad" : object.filtro.campo === 'solicitante__nombre' ? "Solicitante" : object.filtro.campo === 'timestamp' ? "Fecha" : object.filtro.label}: + + + + {`${texto(object.filtro.campo, object.filtro.html_type, object.filtro.lookups)}`} + +
+
+ handleRemoveFilter(object.filtro.campo, key)} + > + close + +
+
+ + ))} +
+ )} + + ); } \ No newline at end of file diff --git a/cosiap_frontend/src/components/common/layouts/LayoutBaseNavigation.jsx b/cosiap_frontend/src/components/common/layouts/LayoutBaseNavigation.jsx index bd0869a..0cebc36 100644 --- a/cosiap_frontend/src/components/common/layouts/LayoutBaseNavigation.jsx +++ b/cosiap_frontend/src/components/common/layouts/LayoutBaseNavigation.jsx @@ -40,12 +40,6 @@ const linksItemsSolicitante = [ navigate: '/historial', isSelected: false }, - { - nameIcon: 'mark_as_unread', - nameItem: 'Recepción de apoyo', - navigate: '/recepcion_apoyo', - isSelected: false - }, { nameIcon: 'help', nameItem: 'Ayuda', diff --git a/cosiap_frontend/tailwind.config.js b/cosiap_frontend/tailwind.config.js index 59bf5d3..004dad4 100644 --- a/cosiap_frontend/tailwind.config.js +++ b/cosiap_frontend/tailwind.config.js @@ -1,4 +1,9 @@ /** @type {import('tailwindcss').Config} */ +import typography from '@tailwindcss/typography'; +import forms from '@tailwindcss/forms'; +import aspectRatio from '@tailwindcss/aspect-ratio'; +import containerQueries from '@tailwindcss/container-queries'; + export default { content: [ "./src/**/*.{html,js,jsx}", @@ -15,9 +20,9 @@ export default { }, }, plugins: [ - require('@tailwindcss/typography'), - require('@tailwindcss/forms'), - require('@tailwindcss/aspect-ratio'), - require('@tailwindcss/container-queries'), + typography, + forms, + aspectRatio, + containerQueries, ], -} \ No newline at end of file +}; -- GitLab