From 84c4a3eb363c1b8ab30c0cfd2604890a85c1361b Mon Sep 17 00:00:00 2001 From: AdalbertoCV <34152734@uaz.edu.mx> Date: Mon, 7 Oct 2024 13:33:38 -0600 Subject: [PATCH] Formatos defalut de minutas y convenios --- cosiap_api/solicitudes/admin.py | 29 ++++- cosiap_api/solicitudes/models.py | 47 ++++++-- cosiap_frontend/src/App.jsx | 3 +- .../common/utility/ReusableTable.css | 32 ++++++ .../common/utility/ReusableTable.jsx | 30 ++++++ .../src/components/modalidades/Modalidad.css | 100 ++++++++++++------ .../src/components/modalidades/Modalidad.jsx | 4 +- .../solicitudes/HistorialSolicitudes.css | 44 ++++++++ .../solicitudes/HistorialSolicitudes.jsx | 96 +++++++++++++++++ 9 files changed, 337 insertions(+), 48 deletions(-) create mode 100644 cosiap_frontend/src/components/common/utility/ReusableTable.css create mode 100644 cosiap_frontend/src/components/common/utility/ReusableTable.jsx create mode 100644 cosiap_frontend/src/components/solicitudes/HistorialSolicitudes.css create mode 100644 cosiap_frontend/src/components/solicitudes/HistorialSolicitudes.jsx diff --git a/cosiap_api/solicitudes/admin.py b/cosiap_api/solicitudes/admin.py index 65dd704..8fbecce 100644 --- a/cosiap_api/solicitudes/admin.py +++ b/cosiap_api/solicitudes/admin.py @@ -1,8 +1,31 @@ from django.contrib import admin from .models import Solicitud, Minuta, Convenio -# Register your models here. +class MinutaAdmin(admin.ModelAdmin): + # Definir los campos que serán mostrados en el administrador + list_display = ('id', 'archivo', 'get_formato') + + # Campo solo lectura para `_formato` + readonly_fields = ('get_formato',) + + def get_formato(self, obj): + return Minuta.get_formato() + get_formato.short_description = 'Formato Minuta' + +class ConvenioAdmin(admin.ModelAdmin): + # Definir los campos que serán mostrados en el administrador + list_display = ('id', 'archivo', 'get_formato') + + # Campo solo lectura para `_formato` + readonly_fields = ('get_formato',) + + def get_formato(self, obj): + return Convenio.get_formato() + get_formato.short_description = 'Formato Convenio' + +# Registrar los modelos en el administrador +admin.site.register(Minuta, MinutaAdmin) +admin.site.register(Convenio, ConvenioAdmin) admin.site.register(Solicitud) -admin.site.register(Minuta) -admin.site.register(Convenio) \ No newline at end of file + diff --git a/cosiap_api/solicitudes/models.py b/cosiap_api/solicitudes/models.py index c7a10d1..d3a085d 100644 --- a/cosiap_api/solicitudes/models.py +++ b/cosiap_api/solicitudes/models.py @@ -27,7 +27,7 @@ class Minuta(models.Model): Devuelve el formato común de todas las instancias de Minuta. ''' if cls._formato is None: - formato_minuta = DynamicFormat.objects.filter(nombre="formato_minuta_defult").first() + formato_minuta = DynamicFormat.objects.filter(nombre="formato_minuta_default").first() cls._formato = formato_minuta return cls._formato @@ -39,6 +39,21 @@ class Minuta(models.Model): ''' cls._formato = value + def save(self, *args, **kwargs): + ''' + Sobrescribe el método save para asegurarse de que el formato + se asigne al crear o actualizar la instancia. + ''' + # Intenta encontrar el formato dinámico al guardar la instancia + formato_minuta = DynamicFormat.objects.filter(nombre="formato_minuta_default").first() + if formato_minuta: + self._formato = formato_minuta + else: + self._formato = None + + # Llama al método save original + super(Minuta, self).save(*args, **kwargs) + def __str__(self): return f'Minuta {self.pk}' @@ -56,8 +71,8 @@ class Convenio(models.Model): - *archivo*: Archivo del convenio. Métodos: - - *get_formato()*: Devuelve el formato común de todas las instancias de Minuta. - - *set_formato(value)*: Establece el formato común para todas las instancias de Minuta. + - *get_formato()*: Devuelve el formato común de todas las instancias de Convenio. + - *set_formato(value)*: Establece el formato común para todas las instancias de Convenio. ''' archivo = models.FileField(upload_to=nombre_archivo_convenio, validators=[validador_archivo_1MB, validador_pdf], verbose_name="Archivo") # Atributo de clase común a todas las instancias @@ -69,27 +84,43 @@ class Convenio(models.Model): Devuelve el formato común de todas las instancias de Convenio. ''' if cls._formato is None: - formato_convenio = DynamicFormat.objects.filter(nombre="formato_convenio_defult").first() + formato_convenio = DynamicFormat.objects.filter(nombre="formato_convenio_default").first() cls._formato = formato_convenio return cls._formato @classmethod def set_formato(cls, value): - '''Establece el formato común para todas las instancias de Minuta. + '''Establece el formato común para todas las instancias de Convenio. Args: - value: El nuevo valor del formato. ''' cls._formato = value + def save(self, *args, **kwargs): + ''' + Sobrescribe el método save para asegurarse de que el formato + se asigne al crear o actualizar la instancia. + ''' + # Intenta encontrar el formato dinámico al guardar la instancia + formato_convenio = DynamicFormat.objects.filter(nombre="formato_convenio_default").first() + if formato_convenio: + self._formato = formato_convenio + else: + self._formato = None + + # Llama al método save original + super(Convenio, self).save(*args, **kwargs) + def __str__(self): - return f'Minuta {self.pk}' + return f'Convenio {self.pk}' class Meta: - verbose_name = "Minuta" - verbose_name_plural = "Minutas" + verbose_name = "Convenio" + verbose_name_plural = "Convenios" ordering = ['pk'] + class Solicitud(models.Model): """ Modelo que contiene la información de referencia de una solicitud. diff --git a/cosiap_frontend/src/App.jsx b/cosiap_frontend/src/App.jsx index a5d5132..93d6d55 100644 --- a/cosiap_frontend/src/App.jsx +++ b/cosiap_frontend/src/App.jsx @@ -28,6 +28,7 @@ import CreateModalidad from "./components/modalidades/CrearModalidad"; 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"; function App() { const [viewPageLoader, setViewPageLoader] = useState(false); @@ -98,7 +99,7 @@ function RoutesApp({ setViewPageLoader }) { element={} /> } /> - } /> + } /> } /> } /> } /> diff --git a/cosiap_frontend/src/components/common/utility/ReusableTable.css b/cosiap_frontend/src/components/common/utility/ReusableTable.css new file mode 100644 index 0000000..a7f4515 --- /dev/null +++ b/cosiap_frontend/src/components/common/utility/ReusableTable.css @@ -0,0 +1,32 @@ +.table-container { + border-radius: 15px; + overflow: hidden; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + margin: 20px; + background-color: #fff; + } + + .table { + width: 100%; + border-collapse: collapse; + } + + .table th { + background-color: brown; + color: #fff; + padding: 12px; + text-align: left; + font-size: 16px; + } + + .table td { + padding: 12px; + text-align: left; + font-size: 14px; + border-bottom: 1px solid #ddd; + } + + .table tr:nth-child(even) { + background-color: #f9f9f9; /* Color para filas pares */ + } + \ No newline at end of file diff --git a/cosiap_frontend/src/components/common/utility/ReusableTable.jsx b/cosiap_frontend/src/components/common/utility/ReusableTable.jsx new file mode 100644 index 0000000..7cc44b1 --- /dev/null +++ b/cosiap_frontend/src/components/common/utility/ReusableTable.jsx @@ -0,0 +1,30 @@ +import '@/components/common/utility/ReusableTable.css'; // Asegúrate de que la ruta sea correcta + +const Tabla = ({ columnas, datos }) => { + return ( + + + + + {columnas.map((columna, index) => ( + {columna.label} + ))} + + + + {datos.map((fila, index) => ( + + {columnas.map((columna, colIndex) => ( + + {columna.render(fila)} + + ))} + + ))} + + + + ); +}; + +export default Tabla; \ No newline at end of file diff --git a/cosiap_frontend/src/components/modalidades/Modalidad.css b/cosiap_frontend/src/components/modalidades/Modalidad.css index 56eb7ca..e6111a1 100644 --- a/cosiap_frontend/src/components/modalidades/Modalidad.css +++ b/cosiap_frontend/src/components/modalidades/Modalidad.css @@ -7,21 +7,25 @@ .file-input-class { width: 100%; padding: 10px; + color: rgb(0, 0, 0); margin: 10px 0; font-size: 16px; - border: 1px solid #ccc; + border: 2px solid #555; /* Aumentamos el grosor y un color más oscuro */ border-radius: 4px; box-sizing: border-box; + transition: border-color 0.3s ease; } +/* Efecto de enfoque (focus) para resaltarlos cuando el usuario interactúe */ .input-class:focus, .textarea-class:focus, .select-class:focus, .date-input-class:focus, .time-input-class:focus, .file-input-class:focus { - border-color: #4CAF50; - outline: none; + border-color: #4CAF50; /* Cambia el color del borde al interactuar */ + outline: none; /* Elimina el borde por defecto del navegador */ + box-shadow: 0 0 5px rgba(76, 175, 80, 0.5); /* Efecto sutil de sombra verde */ } /* Estilos para el textarea */ @@ -36,20 +40,49 @@ background-color: white; } -/* Estilos para grupos de casillas (checkbox) */ +/* Grupo de casillas: Mantener una distribución compacta */ .checkbox-group { display: flex; flex-wrap: wrap; + gap: 10px; /* Espacio entre las casillas */ } +/* Etiquetas de las casillas */ .checkbox-label { + display: flex; + align-items: center; margin-right: 15px; + font-size: 16px; /* Tamaño de texto estándar */ } -.checkbox-label input { - margin-right: 5px; +/* Estilo personalizado para el checkbox */ +.checkbox-label input[type="checkbox"] { + width: 20px; /* Tamaño del checkbox */ + height: 20px; /* Tamaño del checkbox */ + margin-right: 5px; /* Espacio entre la casilla y el texto */ + cursor: pointer; + border: 2px solid #333; /* Borde visible */ + border-radius: 4px; /* Esquinas redondeadas */ + appearance: none; /* Eliminar el estilo por defecto del navegador */ + position: relative; } +/* Estilo cuando está seleccionado */ +.checkbox-label input[type="checkbox"]:checked { + background-color: #333; /* Fondo oscuro cuando está seleccionado */ +} + +/* Agregar un pseudo-elemento para el 'checkmark' */ +.checkbox-label input[type="checkbox"]:checked::after { + color: white; + font-size: 14px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + + /* Estilos para el input de archivo */ .file-input-class { cursor: pointer; @@ -142,7 +175,7 @@ font-size: 1.8rem; font-weight: bold; margin-bottom: 10px; - color: var(--secundario); + color: brown; text-align: center; } @@ -198,7 +231,7 @@ font-size: 1.8rem; font-weight: bold; margin-bottom: 10px; - color: var(--secundario); + color: brown; text-align: center; } @@ -208,33 +241,32 @@ margin-top: 20px; } - /* Botones de acción */ - .buttons-container { - display: flex; - justify-content: center; - gap: 20px; - margin-top: 30px; - } - - .button { - background-color: var(--secundario); - color: var(--blanco); - font-weight: bold; - padding: 10px 20px; - border-radius: 10px; - cursor: pointer; - transition: background-color 0.3s ease; - } +/* Botones de acción */ +.buttons-container { + display: flex; + justify-content: space-between; /* Un botón a la izquierda, otro a la derecha */ + width: 100%; /* El contenedor ocupa todo el ancho disponible */ + margin-top: 20px; + padding: 0 40px; /* Espacio a los lados del contenedor, ajusta según lo necesites */ + box-sizing: border-box; /* Asegura que el padding no afecte el ancho total */ +} + +.button, .submit-button { + background-color: var(--secundario); + color: var(--blanco); + font-weight: bold; + font-style: normal; + padding: 10px 20px; + border-radius: 10px; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.submit-button { + background-color: #4CAF50; +} + - .submit-button { - background-color: #4CAF50; - color: var(--blanco); - font-weight: bold; - padding: 10px 20px; - border-radius: 10px; - cursor: pointer; - transition: background-color 0.3s ease; - } .button:hover { diff --git a/cosiap_frontend/src/components/modalidades/Modalidad.jsx b/cosiap_frontend/src/components/modalidades/Modalidad.jsx index 2a3bfd8..64378f4 100644 --- a/cosiap_frontend/src/components/modalidades/Modalidad.jsx +++ b/cosiap_frontend/src/components/modalidades/Modalidad.jsx @@ -306,8 +306,8 @@ const SolicitarModalidad = () => { navigate('/modalidades')}> Cancelar - - Solicitar + + Enviar Solicitud diff --git a/cosiap_frontend/src/components/solicitudes/HistorialSolicitudes.css b/cosiap_frontend/src/components/solicitudes/HistorialSolicitudes.css new file mode 100644 index 0000000..208f84b --- /dev/null +++ b/cosiap_frontend/src/components/solicitudes/HistorialSolicitudes.css @@ -0,0 +1,44 @@ +/* Estilo para los estados */ +.estado-label { + padding: 5px 10px; + border-radius: 8px; + color: white; + font-weight: bold; + display: inline-block; + text-align: center; + min-width: 100px; +} + +.estado-aprobado { + background-color: rgb(126, 233, 126); +} + +.estado-rechazado { + background-color: rgb(223, 91, 91); +} + +.estado-pendiente { + background-color: rgb(109, 163, 216); + color: black; +} + + +.button-ver{ + background-color: grey; + color: white; + padding: 5px 10px; + border-radius: 5px; + margin-right: 10px; + border: none; + cursor: pointer; +} + +.button-editar{ + background-color: brown; + color: white; + padding: 5px 10px; + border-radius: 5px; + margin-right: 10px; + border: none; + cursor: pointer; +} \ No newline at end of file diff --git a/cosiap_frontend/src/components/solicitudes/HistorialSolicitudes.jsx b/cosiap_frontend/src/components/solicitudes/HistorialSolicitudes.jsx new file mode 100644 index 0000000..3753cda --- /dev/null +++ b/cosiap_frontend/src/components/solicitudes/HistorialSolicitudes.jsx @@ -0,0 +1,96 @@ +import { useState, useEffect } from "react"; +import api from '../../api'; +import Tabla from "../common/utility/ReusableTable"; // Importa la tabla reutilizable +import MainContainer from "../common/utility/MainContainer"; +import '@/components/solicitudes/HistorialSolicitudes.css'; // Importa los estilos + +// Componente para devolver la lista de las solicitudes de un solicitante +const ListaSolicitudes = () => { + const [solicitudes, setSolicitudes] = useState([]); + + // Obtenemos las solicitudes del usuario + useEffect(() => { + const fetchSolicitudes = async () => { + try { + const response = await api.solicitudes.historial.get(); + setSolicitudes(Object.values(response.data) || []); + } catch (error) { + console.error("Error al obtener la lista de solicitudes", error); + setSolicitudes([]); + } + }; + fetchSolicitudes(); + }, []); + + // Función para aplicar estilos al estado + const getStatusClass = (status) => { + switch (status) { + case 'Aprobado': + return 'estado-aprobado'; + case 'Rechazado': + return 'estado-rechazado'; + case 'Pendiente': + return 'estado-pendiente'; + default: + return ''; + } + }; + + // Definimos las columnas a mostrar en la tabla + const columnas = [ + { + label: "Nº de Solicitud", + render: (fila) => fila.solicitud_n + }, + { + label: "Estatus", + render: (fila) => ( + + {fila.status} + + ) + }, + { + label: "Monto Solicitado", + render: (fila) => `$${fila.monto_solicitado}` + }, + { + label: "Monto Aprobado", + render: (fila) => `$${fila.monto_aprobado}` + }, + { + label: "Fecha", + render: (fila) => { + const fecha = new Date(fila.timestamp); + return `${fecha.getDate()}/${fecha.getMonth() + 1}/${fecha.getFullYear()}`; + } + }, + { + label: "Acciones", + render: (fila) => ( + + + Ver + + {fila.status === 'Pendiente' && ( + + Editar + + )} + + ) + } + ]; + + return ( + + + + + + ); +}; + +export default ListaSolicitudes; -- GitLab