diff --git a/cosiap_frontend/src/App.css b/cosiap_frontend/src/App.css index b6ec5a7be721fc86e95919ca14b935a61a79b0d0..61cc03bcd74ba8b63684cb059d233fe065e863d6 100644 --- a/cosiap_frontend/src/App.css +++ b/cosiap_frontend/src/App.css @@ -829,4 +829,67 @@ th, td { white-space: nowrap; } +.context-menu { + position: absolute; + background-color: rgb(232, 230, 230); + border: 1px solid #ccc; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + padding: 8px; + z-index: 100; +} + +.context-menu button { + display: block; + width: 100%; + padding: 8px; + border: none; + background: none; + text-align: left; + cursor: pointer; +} + +.context-menu button:hover { + background-color: #f0f0f0; +} + +/* Agrega estos estilos a tu archivo CSS */ +.confirm-delete-menu { + position: fixed; /* Cambiado a fixed para centrar en la pantalla */ + top: 50%; + left: 50%; + transform: translate(-50%, -50%); /* Centra el menú */ + background-color: white; /* Color de fondo */ + border-radius: 8px; /* Bordes redondeados */ + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* Sombra para dar profundidad */ + padding: 20px; /* Espaciado interno */ + z-index: 1000; /* Asegúrate de que el menú se muestre por encima de otros elementos */ + text-align: center; /* Centra el texto */ +} + +.confirm-delete-menu p { + margin: 0 0 20px; /* Margen debajo del texto */ + font-size: 16px; /* Tamaño de fuente */ +} +.confirm-delete-menu button { + margin: 0 10px; /* Espaciado entre botones */ + padding: 10px 15px; /* Espaciado interno en botones */ + border: none; /* Sin borde */ + border-radius: 5px; /* Bordes redondeados en botones */ + cursor: pointer; /* Cambia el cursor al pasar sobre el botón */ + font-size: 14px; /* Tamaño de fuente */ +} + +.confirm-delete-menu button.cancel { + background-color: #ccc; /* Color gris para el botón de cancelar */ + color: #333; /* Color de texto */ +} + +.confirm-delete-menu button.confirm { + background-color: #ff4d4d; /* Color rojo para el botón de confirmar */ + color: white; /* Color de texto blanco */ +} + +.confirm-delete-menu button:hover { + opacity: 0.9; /* Efecto hover */ +} diff --git a/cosiap_frontend/src/App.jsx b/cosiap_frontend/src/App.jsx index a64f2280a1717f0faeb2175e73faf32ab079f33e..7a5f4bdb85bafe2f7e6f905a229090cee4a3f731 100644 --- a/cosiap_frontend/src/App.jsx +++ b/cosiap_frontend/src/App.jsx @@ -35,6 +35,7 @@ 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"; function App() { const [viewPageLoader, setViewPageLoader] = useState(false); @@ -110,7 +111,7 @@ function RoutesApp({ setViewPageLoader }) { } /> } /> } /> - } /> + } /> } /> {/* Solo administradores pueden acceder a estas url */} @@ -123,6 +124,8 @@ function RoutesApp({ setViewPageLoader }) { } /> } /> } /> + } /> + } /> diff --git a/cosiap_frontend/src/components/admin/HistorialAdmin.jsx b/cosiap_frontend/src/components/admin/HistorialAdmin.jsx new file mode 100644 index 0000000000000000000000000000000000000000..7f0b1105c7bdeff49f641f61714872e854c3063a --- /dev/null +++ b/cosiap_frontend/src/components/admin/HistorialAdmin.jsx @@ -0,0 +1,120 @@ +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 '@/App.css'; +import { useNavigate, useParams} from 'react-router-dom'; + + +// Componente para devolver la lista de las solicitudes de un solicitante +const ListaSolicitudesSolicitante = () => { + const { id } = useParams(); + const [solicitudes, setSolicitudes] = useState([]); + const [modalidades, setModalidades] = useState([]); + const navigate = useNavigate(); + + // Obtenemos las solicitudes del usuario + useEffect(() => { + const fetchSolicitudes = async () => { + try { + const response = await api.solicitudes.historial.getById(id); + const solicitudesData = Object.values(response.data) || []; + + // Guardamos las solicitudes + setSolicitudes(solicitudesData); + + // Creamos un objeto para almacenar los nombres de las modalidades por ID de solicitud + const modalidadNombres = {}; + + // Realizamos las solicitudes a la API para obtener el nombre de cada modalidad + await Promise.all(solicitudesData.map(async (solicitud) => { + const modalidadId = solicitud.modalidad; + try { + const modalidadResponse = await api.modalidades.getById(modalidadId); + // Asignamos el nombre de la modalidad en el objeto usando el ID de la solicitud como clave + modalidadNombres[solicitud.id] = modalidadResponse.data.data.nombre; + } catch (error) { + console.error(`Error al obtener el nombre de la modalidad con id ${modalidadId}`, error); + modalidadNombres[solicitud.id] = 'Desconocido'; + } + })); + + // Actualizamos el estado con los nombres de las modalidades, almacenados por ID + setModalidades(modalidadNombres); + + } 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: "Modalidad", + render: (fila) => modalidades[fila.id] || 'Cargando...' + }, + { + 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) => ( +
+ +
+ ) + } + ]; + + return ( + +
+ +
+
+ ); +}; + +export default ListaSolicitudesSolicitante; diff --git a/cosiap_frontend/src/components/admin/TablaUsuarios.jsx b/cosiap_frontend/src/components/admin/TablaUsuarios.jsx index 4f6cffa31235e7be5f38da673288e64720b3f237..ebaeef7124e97bca19b4ac87764cd4937f3afda3 100644 --- a/cosiap_frontend/src/components/admin/TablaUsuarios.jsx +++ b/cosiap_frontend/src/components/admin/TablaUsuarios.jsx @@ -3,6 +3,7 @@ import api from '../../api'; import Tabla from "../common/utility/ReusableTable"; import MainContainer from "../common/utility/MainContainer"; import '@/App.css'; +import { useNavigate } from "react-router-dom"; const ListaUsuarios = () => { const [usuarios, setUsuarios] = useState([]); @@ -13,6 +14,9 @@ const ListaUsuarios = () => { const tableContainerRef = useRef(null); // Referencia al contenedor con scroll const [alertMessage, setAlertMessage] = useState(''); const [isSuccess, setIsSuccess] = useState(false); + const [selectedRow, setSelectedRow] = useState(null); // Para abrir el menú contextual + const [isConfirmingDelete, setIsConfirmingDelete] = useState(false); // Estado para controlar la confirmación de eliminación + const navigate = useNavigate(); useEffect(() => { const fetchUsuarios = async () => { @@ -27,6 +31,42 @@ const ListaUsuarios = () => { fetchUsuarios(); }, []); + const handleSingleClick = (fila) => { + setSelectedRow(fila.id); // Muestra el menú para el registro seleccionado + }; + + const handleCloseMenu = () => { + setSelectedRow(null); + setIsConfirmingDelete(false); // Cierra el menú de confirmación + }; + + const handleEditRow = (id) => { + setEditRow(id); + handleCloseMenu(); + }; + + const handleNavigateToHistorial = (id) => { + console.log(id); + navigate(`/historial-solicitante/${id}`); + }; + + const handleDeleteConfirm = (id) => { + setIsConfirmingDelete(id); // Abre el menú de confirmación + }; + + const handleDelete = async (id) => { + try { + await api.usuarios.delete(id); + const response = await api.usuarios.solicitantes.get(); + setUsuarios(response.data.data); + showAlert("Usuario eliminado con éxito", true); + } catch (error) { + console.error("Error al eliminar usuario", error); + showAlert("Error al eliminar el usuario", false); + } + handleCloseMenu(); // Cierra el menú después de eliminar + }; + useEffect(() => { const handleClickOutside = (event) => { if (tableContainerRef.current?.contains(event.target)) { @@ -42,6 +82,7 @@ const ListaUsuarios = () => { setEditRow(null); setRegisterChange({}); } + handleCloseMenu(); }; document.addEventListener("mousedown", handleClickOutside); @@ -91,7 +132,13 @@ const ListaUsuarios = () => { autoFocus /> ) : ( - setEditRow(fila.id)}>{fila[key]} + handleSingleClick(fila)} + onDoubleClick={() => handleEditRow(fila.id)} + style={{ cursor: "pointer" }} + > + {fila[key]} + ); }; @@ -136,9 +183,9 @@ const ListaUsuarios = () => { return ( {alertMessage && ( -
- {alertMessage} -
+
+ {alertMessage} +
)}
{
- + + {selectedRow && !isConfirmingDelete && ( +
+ + +
+ )} + {isConfirmingDelete && ( +
+

¿Estás seguro de que deseas eliminar este usuario?

+ + +
+ )}
); }; export default ListaUsuarios; + diff --git a/cosiap_frontend/src/components/solicitudes/VerSolicitud.jsx b/cosiap_frontend/src/components/solicitudes/VerSolicitud.jsx index f699c887e28837d574e2d8664e72cdb9f9697872..53334d7ed42281fc6ed995c1768923dda7b889aa 100644 --- a/cosiap_frontend/src/components/solicitudes/VerSolicitud.jsx +++ b/cosiap_frontend/src/components/solicitudes/VerSolicitud.jsx @@ -1,7 +1,7 @@ import { useState, useEffect } from "react"; import api from '../../api'; import MainContainer from "../common/utility/MainContainer"; -import { useParams, useNavigate } from 'react-router-dom'; +import { useParams} from 'react-router-dom'; import { renderElemento } from "@/components/common/utility/RenderElementView"; import '@/App.css'; @@ -11,7 +11,6 @@ const VisualizarSolicitud = () => { const [solicitud, setSolicitud] = useState(null); const [modalidad, setModalidad] = useState(''); const [respuesta, setRespuesta] = useState([]); - const navigate = useNavigate(); // Obtenemos los datos de la solicitud useEffect(() => { @@ -86,12 +85,6 @@ const VisualizarSolicitud = () => {
{renderSecciones()}
- {/* Botones de acción */} -
- -
)}