diff --git a/cosiap_api/administracion/views.py b/cosiap_api/administracion/views.py index 92d87567395de02e1834babf2db436edfec94b60..2513f625bc3e2008c16f4f867f6086faa95f0fa1 100644 --- a/cosiap_api/administracion/views.py +++ b/cosiap_api/administracion/views.py @@ -13,7 +13,7 @@ class AbrirCerrarConvocatoria(BasePermissionAPIView): APIView con la lógica para abrir la convocatoria. ''' - permission_classes_list = [IsAuthenticated, es_admin] + permission_classes_list = [IsAuthenticated] permission_classes_update = [IsAuthenticated, es_admin] def get(self, request, *args, **kwargs): diff --git a/cosiap_api/static/images/Admin_Home.jpeg b/cosiap_api/static/images/Admin_Home.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..0c824d60b22ec9e9cf31ad8b42f8833d947075cb Binary files /dev/null and b/cosiap_api/static/images/Admin_Home.jpeg differ diff --git a/cosiap_api/static/images/Solicitante_Home.webp b/cosiap_api/static/images/Solicitante_Home.webp new file mode 100644 index 0000000000000000000000000000000000000000..7f99ac6fd9cf674d696c4e735b94f3e31bc0f725 Binary files /dev/null and b/cosiap_api/static/images/Solicitante_Home.webp differ diff --git a/cosiap_api/static/images/logo.jpg b/cosiap_api/static/images/logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4fa94824569f6a8b813e57b5f8f9d5ec318e825f Binary files /dev/null and b/cosiap_api/static/images/logo.jpg differ diff --git a/cosiap_frontend/src/App.css b/cosiap_frontend/src/App.css index b090b5c59c21e10f39c27f2fb08ea721a47016e8..b38c3e9d95644db0b73724fff6c3941552f80bbc 100644 --- a/cosiap_frontend/src/App.css +++ b/cosiap_frontend/src/App.css @@ -1110,4 +1110,124 @@ th, td { justify-content: center; gap: 8px; transition: background-color 0.3s ease; +} + +.toggle-switch { + position: relative; + display: inline-block; + width: 50px; + height: 24px; +} + +.toggle-switch input { + opacity: 0; + width: 0; + height: 0; +} + +.toggle-switch .slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + transition: 0.4s; + border-radius: 24px; +} + +.toggle-switch .slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 4px; + bottom: 3px; + background-color: white; + transition: 0.4s; + border-radius: 50%; +} + +.toggle-switch input:checked + .slider { + background-color: #4caf50; +} + +.toggle-switch input:checked + .slider:before { + transform: translateX(26px); +} + +.toggle-switch .slider:active:before { + width: 22px; +} + +.home-solicitante-container { + display: flex; + justify-content: center; + align-items: center; + padding: 20px; + background-color: #f9e8d7; + border-radius: 16px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +.home-admin-container { + display: flex; + justify-content: center; + align-items: center; + padding: 20px; + background-color: #e4896f; + border-radius: 16px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +.info-card { + flex: 1; + padding: 20px; + max-width: 50%; + justify-content: center; +} + +.title { + color: #8d2f2f; + font-size: 24px; + font-weight: bold; + margin-bottom: 16px; +} + +.home-ul { + list-style-type: disc; + margin-top: 12px; + padding-left: 40px; +} + +.home-ul li { + margin-bottom: 8px; + font-size: 16px; + line-height: 1.5; +} + +.start-button { + background-color: #8d2f2f; + color: white; + border: none; + padding: 10px 20px; + border-radius: 8px; + font-size: 16px; + margin-top: 16px; + cursor: pointer; +} + +.home-image-container { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + padding: 20px; +} + +.home-image-container img { + max-width: 100%; + height: auto; + border-radius: 16px; } \ No newline at end of file diff --git a/cosiap_frontend/src/api.js b/cosiap_frontend/src/api.js index 3a680a4215701d69336ea038825d395b62e29a66..4b9837076f0312f54da6b432dfdf58e2432d457b 100644 --- a/cosiap_frontend/src/api.js +++ b/cosiap_frontend/src/api.js @@ -191,6 +191,10 @@ const api = { updateMinuta: (data) => ax.put('api/plantillas/minuta/',data), getConvenio: () => ax.get('api/plantillas/convenio'), updateConvenio: (data) => ax.put('api/plantillas/convenio/',data), + }, + convocatoria: { + get: () => ax.get('api/administracion/convocatoria/'), + put: (data) => ax.put('api/administracion/convocatoria/', data) } }; diff --git a/cosiap_frontend/src/components/Inicio.jsx b/cosiap_frontend/src/components/Inicio.jsx index 16cacd1ea86cbfb92e04ac975d25cf57c0bfd76c..64541c48eb29971998fe8db77b4bc351fd1675bb 100644 --- a/cosiap_frontend/src/components/Inicio.jsx +++ b/cosiap_frontend/src/components/Inicio.jsx @@ -1,10 +1,63 @@ +import { useAutenticacion } from "@/components/common/utility/Autenticador"; +import MainContainer from "@/components/common/utility/MainContainer"; +import '@/App.css' +import { useNavigate } from 'react-router-dom'; export default function Inicio() { - + const { isAdmin } = useAutenticacion(); + const navigate = useNavigate(); return ( - <> - - + + {isAdmin ? ( +
+
+

SISTEMA COSIAP

+

+ Como Administrador podrás: +

+

+
    +
  • Gestionar las modalidades de apoyo pertenecientes a una convocatoria.
  • +
  • Gestionar usuarios (Administradores y Solicitantes)
  • +
  • Gestionar solicitudes de apoyos.
  • +
  • Gestionar reportes de solicitudes (filtros y configuraciones personalizadas)
  • +
  • Gestionar formatos personalizados para la realizacion de solicitudes.
  • +
+

+ +
+
+ Home Solicitante +
+
+ ) : ( +
+
+

SISTEMA COSIAP

+

+ Como solicitante podrás aspirar a un apoyo económico relacionado con + una modalidad vigente en el periodo actual. +

+

+
    +
  • Existe la posibilidad de que tu solicitud sea rechazada.
  • +
  • No puedes adquirir más de dos apoyos en menos de un año.
  • +
+

+ +
+
+ Home Solicitante +
+
+ )} +
); -} \ 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 fa94ef28e4ba933d1bec62b9454f7ff61cc2f05b..0db9c6f7a5f388f0d3c0873619945f01ac60824e 100644 --- a/cosiap_frontend/src/components/common/layouts/LayoutBaseNavigation.jsx +++ b/cosiap_frontend/src/components/common/layouts/LayoutBaseNavigation.jsx @@ -6,75 +6,31 @@ import SettingsToggle from "@/components/common/layouts/LayoutsNavigation/Settin import Settings from "@/components/common/layouts/LayoutsNavigation/Settings"; import { useAutenticacion } from "@/components/common/utility/Autenticador"; -const linksItemsAdmin=[ - { - nameIcon: 'library_books', - nameItem: 'Modalidades', - navigate: '/modalidades', - isSelected: false - }, - { - nameIcon: 'group', - nameItem: 'Usuarios', - navigate: '/usuarios', - isSelected: false - }, - { - nameIcon: 'list_alt', - nameItem: 'Solicitudes', - navigate: '/solicitudes', - isSelected: false - }, - { - nameIcon: 'description', - nameItem: 'Formatos', - navigate: '/formatos', - isSelected: false - }, +const linksItemsAdmin = [ + { nameIcon: 'library_books', nameItem: 'Modalidades', navigate: '/modalidades', isSelected: false }, + { nameIcon: 'group', nameItem: 'Usuarios', navigate: '/usuarios', isSelected: false }, + { nameIcon: 'list_alt', nameItem: 'Solicitudes', navigate: '/solicitudes', isSelected: false }, + { nameIcon: 'description', nameItem: 'Formatos', navigate: '/formatos', isSelected: false }, ]; const linksItemsSolicitante = [ - { - nameIcon: 'library_books', - nameItem: 'Modalidades', - navigate: '/modalidades', - isSelected: false + { nameIcon: 'library_books', nameItem: 'Modalidades', navigate: '/modalidades', isSelected: false }, + { nameIcon: 'history', nameItem: 'Historial', navigate: '/historial', isSelected: false }, + { + nameIcon: 'help', nameItem: 'Ayuda', isSelected: false, + subMenu: [{ nameItem: 'Manual', isSelected: false, navigate: '/ayuda/manual' }] }, - { - nameIcon: 'history', - nameItem: 'Historial', - navigate: '/historial', - isSelected: false - }, - { - nameIcon: 'help', - nameItem: 'Ayuda', - isSelected: false, - subMenu: [ - { - nameItem: 'Manual', - isSelected: false, - navigate: '/ayuda/manual', - } - ] - }, - { - nameIcon: 'account_circle', - nameItem: 'Perfil', - navigate: '/perfil', - isSelected: false - } + { nameIcon: 'account_circle', nameItem: 'Perfil', navigate: '/perfil', isSelected: false }, ]; export default function LayoutBaseNavigation() { const [viewMenu, setViewMenu] = useState(false); const [selectedNav, setSelectedNav] = useState('vertical'); const [viewSettings, setViewSettings] = useState(false); - const {isAdmin} = useAutenticacion(); - const [links, setLinks] = useState(isAdmin != undefined && isAdmin ? linksItemsAdmin : linksItemsSolicitante); + const { isAdmin } = useAutenticacion(); + const [links, setLinks] = useState(isAdmin ? linksItemsAdmin : linksItemsSolicitante); const settingsRef = useRef(); const settingsToggleRef = useRef(); - const location = useLocation(); const [sectionURL, setSectionURL] = useState(""); @@ -88,69 +44,47 @@ export default function LayoutBaseNavigation() { useEffect(() => { const savedNavStyle = localStorage.getItem('selectedNav'); - if (savedNavStyle) { - setSelectedNav(savedNavStyle); - } + if (savedNavStyle) setSelectedNav(savedNavStyle); }, []); useEffect(() => { localStorage.setItem('selectedNav', selectedNav); }, [selectedNav]); - useEffect(() => { - const updatedLinks = links.map(link => { - const isSelected = link.nameItem.toLowerCase() === sectionURL.toLowerCase() || - link.navigate?.split('/').pop().toLowerCase() === sectionURL.toLowerCase(); - - const updatedSubMenu = link.subMenu?.map(sub => ({ - ...sub, - isSelected: sub.nameItem.toLowerCase() === sectionURL.toLowerCase() || - sub.navigate?.split('/').pop().toLowerCase() === sectionURL.toLowerCase() - })); - - const isParentSelected = updatedSubMenu?.some(sub => sub.isSelected); - - return { - ...link, - isSelected: isSelected || isParentSelected, - ...(updatedSubMenu && { subMenu: updatedSubMenu }) - }; - }); - - setLinks(updatedLinks); - }, [sectionURL]); - useEffect(() => { function handleClickOutside(event) { - if (settingsRef.current && !settingsRef.current.contains(event.target) && - settingsToggleRef.current && !settingsToggleRef.current.contains(event.target)) { + if ( + settingsRef.current && !settingsRef.current.contains(event.target) && + settingsToggleRef.current && !settingsToggleRef.current.contains(event.target) + ) { setViewSettings(false); } } document.addEventListener("mousedown", handleClickOutside); - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; + return () => document.removeEventListener("mousedown", handleClickOutside); }, []); return (
{selectedNav === 'horizontal' && ( - + )} {selectedNav === 'vertical' && ( - + )}
setViewSettings(!viewSettings)} >
+ {viewSettings && ( - +
+ +
)}
diff --git a/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/Settings.jsx b/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/Settings.jsx index 2047f000e1b1a3b6f6ba57d54946282717f29286..0b589ad48644221bc3c9877e4e09c732709bf4bc 100644 --- a/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/Settings.jsx +++ b/cosiap_frontend/src/components/common/layouts/LayoutsNavigation/Settings.jsx @@ -1,4 +1,35 @@ +import '@/App.css' +import api from '@/api'; +import { useEffect, useState } from "react"; +import { useAutenticacion } from "@/components/common/utility/Autenticador"; + export default function Settings( {settingsRef, selectedNav, setSelectedNav} ){ + + const [convocatoria, setConvocatoria] = useState(true); + const { isAdmin } = useAutenticacion(); + + useEffect(() => { + const fetchConvocatoria = async () => { + try { + const response = await api.convocatoria.get(); + setConvocatoria(response.data.convocatoria_is_open); + } catch (error) { + setConvocatoria(false); + } + }; + fetchConvocatoria(); + }, []); + + const handleConvocatoria = async () => { + try { + const nuevaConvocatoria = !convocatoria + await api.convocatoria.put({'nuevo_estado': nuevaConvocatoria}); + setConvocatoria(nuevaConvocatoria); + } catch (error) { + setConvocatoria(false); + } + }; + return (
@@ -24,6 +55,27 @@ export default function Settings( {settingsRef, selectedNav, setSelectedNav} ){ Horizontal
+
+ {/* Renderiza el switch solo si es administrador */} + {isAdmin && ( +
+ +
+ )} +
diff --git a/cosiap_frontend/src/components/modalidades/ListaModalidades.jsx b/cosiap_frontend/src/components/modalidades/ListaModalidades.jsx index 059fcc71f79292d64f50a0cc91e70c43da55e4fb..8681a724a3af5c2d36cbc99708507891514897e8 100644 --- a/cosiap_frontend/src/components/modalidades/ListaModalidades.jsx +++ b/cosiap_frontend/src/components/modalidades/ListaModalidades.jsx @@ -16,6 +16,7 @@ export default function ListaModalidades({ }) { const [modalidades, setModalidades] = useState([]); const navigate = useNavigate(); + const [convocatoria, setConvocatoria] = useState(true); const obtenerModalidades = async () => { setViewPageLoader(true); @@ -35,6 +36,18 @@ export default function ListaModalidades({ obtenerModalidades(); }, []); + useEffect(() => { + const fetchConvocatoria = async () => { + try { + const response = await api.convocatoria.get(); + setConvocatoria(response.data.convocatoria_is_open); + } catch (error) { + setConvocatoria(false); + } + }; + fetchConvocatoria(); +}, []); + return ( {/* Botón centrado debajo del título */} @@ -65,19 +78,23 @@ export default function ListaModalidades({ >
- -
+ {/* Botón de Editar o Solicitar basado en las condiciones */} + {isAdmin ? ( + + ) : convocatoria ? ( + + ) : null} +
); }) diff --git a/cosiap_frontend/src/components/modalidades/Modalidad.jsx b/cosiap_frontend/src/components/modalidades/Modalidad.jsx index f8ae535e699f5d944c8f404ff1aa99723be84630..033d5fb8639e3529fce851ce3dcb2839f4fd620f 100644 --- a/cosiap_frontend/src/components/modalidades/Modalidad.jsx +++ b/cosiap_frontend/src/components/modalidades/Modalidad.jsx @@ -23,6 +23,7 @@ const SolicitarModalidad = () => { const [respuesta, setRespuesta] = useState([]); const [alertMessage, setAlertMessage] = useState(''); // Estado para el mensaje de alerta const [isSuccess, setIsSuccess] = useState(false); + const [convocatoria, setConvocatoria] = useState(true); const navigate = useNavigate(); const obtenerModalidad = async () => { @@ -68,6 +69,21 @@ const SolicitarModalidad = () => { } }; + useEffect(() => { + const fetchConvocatoria = async () => { + try { + const response = await api.convocatoria.get(); + setConvocatoria(response.data.convocatoria_is_open); + if (response.data.convocatoria_is_open === false){ + navigate('/404'); + } + } catch (error) { + setConvocatoria(false); + } + }; + fetchConvocatoria(); + }, []); + useEffect(() => { obtenerModalidad(); @@ -262,7 +278,7 @@ const SolicitarModalidad = () => { return ( - modalidad && ( + (modalidad && convocatoria) && (
{/* Alerta */}