diff --git a/apps/web/src/components/Sidebar.jsx b/apps/web/src/components/Sidebar.jsx index aa025acd..14d3a023 100644 --- a/apps/web/src/components/Sidebar.jsx +++ b/apps/web/src/components/Sidebar.jsx @@ -46,16 +46,16 @@ const SidebarIcon = ({ icon, isDark }) => { ), - favorites: ( - - - - ), - members: ( - - - - ), + // favorites: ( + // + // + // + // ), + // members: ( + // + // + // + // ), admin: ( diff --git a/apps/web/src/config/sidebarConfig.js b/apps/web/src/config/sidebarConfig.js index 15c9a100..d7952c85 100644 --- a/apps/web/src/config/sidebarConfig.js +++ b/apps/web/src/config/sidebarConfig.js @@ -25,11 +25,11 @@ export const SIDEBAR_ITEMS = { label: 'Mis proyectos', icon: 'myProjects' }, - { - path: '/favorite-projects', - label: 'Mis favoritos', - icon: 'favorites' - } + // { + // path: '/favorite-projects', + // label: 'Mis favoritos', + // icon: 'favorites' + // } ], // STUDENT - Puede explorar y aplicar a proyectos @@ -54,11 +54,11 @@ export const SIDEBAR_ITEMS = { label: 'Mis proyectos', icon: 'myProjects' }, - { - path: '/favorite-projects', - label: 'Mis favoritos', - icon: 'favorites' - } + // { + // path: '/favorite-projects', + // label: 'Mis favoritos', + // icon: 'favorites' + // } ], // PROFESSOR - Puede explorar, solicitar y gestionar proyectos @@ -88,16 +88,16 @@ export const SIDEBAR_ITEMS = { label: 'Mis proyectos', icon: 'myProjects' }, - { - path: '/favorite-projects', - label: 'Mis favoritos', - icon: 'favorites' - }, - { - path: '/members', - label: 'Miembros', - icon: 'members' - } + // { + // path: '/favorite-projects', + // label: 'Mis favoritos', + // icon: 'favorites' + // }, + // { + // path: '/members', + // label: 'Miembros', + // icon: 'members' + // } ], // ADMIN - Acceso completo + panel de administración diff --git a/apps/web/src/features/auth/hooks/useCurrentUser.js b/apps/web/src/features/auth/hooks/useCurrentUser.js index dbb694c3..77371b87 100644 --- a/apps/web/src/features/auth/hooks/useCurrentUser.js +++ b/apps/web/src/features/auth/hooks/useCurrentUser.js @@ -7,21 +7,71 @@ const API_URL = import.meta.env.VITE_API_URL; * Hook para obtener la sesión actual del usuario autenticado * @returns {Object} { user, role, isLoading, error } */ +// Variables globales para caché y deduplicación +let cachedUser = null; +let cachedRole = null; +let fetchPromise = null; +let lastFetchTime = 0; +const CACHE_DURATION = 60000; // 1 minuto de caché + +/** + * Hook para obtener la sesión actual del usuario autenticado + * Implementa caché y deduplicación para evitar errores 429 + * @returns {Object} { user, role, isLoading, error } + */ export default function useCurrentUser() { - const [user, setUser] = useState(null); - const [role, setRole] = useState(null); - const [isLoading, setIsLoading] = useState(true); + const [user, setUser] = useState(cachedUser); + const [role, setRole] = useState(cachedRole); + const [isLoading, setIsLoading] = useState(!cachedUser); const [error, setError] = useState(null); useEffect(() => { const fetchCurrentUser = async () => { + // Si tenemos datos en caché y son recientes, no hacer nada + const now = Date.now(); + if (cachedUser && (now - lastFetchTime < CACHE_DURATION)) { + setUser(cachedUser); + setRole(cachedRole); + setIsLoading(false); + return; + } + + // Si ya hay una petición en curso, esperar a que termine + if (fetchPromise) { + try { + const data = await fetchPromise; + setUser(data.user); + setRole(data.role); + } catch (err) { + setError(err.message); + } finally { + setIsLoading(false); + } + return; + } + + // Inicia una nueva petición + setIsLoading(true); + fetchPromise = fetchWithAuth(`${API_URL}/auth/me`) + .then(response => { + cachedUser = response.data.user; + cachedRole = response.data.role; + lastFetchTime = Date.now(); + return { user: cachedUser, role: cachedRole }; + }) + .catch(err => { + console.error("Error fetching current user:", err); + throw err; + }) + .finally(() => { + fetchPromise = null; // Limpia la promesa al terminar + }); + try { - const response = await fetchWithAuth(`${API_URL}/auth/me`); - - setUser(response.data.user); - setRole(response.data.role); + const data = await fetchPromise; + setUser(data.user); + setRole(data.role); } catch (err) { - console.error("Error fetching current user:", err); setError(err.message); } finally { setIsLoading(false); diff --git a/apps/web/src/features/projects/components/EditApplicationModal.jsx b/apps/web/src/features/projects/components/EditApplicationModal.jsx new file mode 100644 index 00000000..ebe3b542 --- /dev/null +++ b/apps/web/src/features/projects/components/EditApplicationModal.jsx @@ -0,0 +1,365 @@ +import { useEffect } from 'react'; +import useEditApplication from '../hooks/useEditApplication'; +import useFormProjectMetadata from '../hooks/useFormProjectMetadata'; +import { formatDateStringSpanish } from '@/utils/dateUtils'; + +export default function EditApplicationModal({ + isOpen, + onClose, + uuid, + application, + onEditSuccess, + onApproveSuccess +}) { + const { faculties, projectTypes, problemTypes } = useFormProjectMetadata(); + + // Buscar la fecha de creación en múltiples ubicaciones posibles + const getApplicationCreatedAt = () => { + if (!application) return undefined; + + return ( + application.createdAt || + application.created_at || + application.requestedAt || + application.requested_at || + application.requestDate || + application.request_date || + application.createdDate || + application.created_date || + application.dueDate + ); + }; + + const applicationCreatedAt = getApplicationCreatedAt(); + + const { + form, + fieldErrors, + isLoading, + handleChange, + handleSaveOnly, + handleSaveAndApprove, + initializeForm, + deadlineConstraints + } = useEditApplication( + uuid, + () => { + onClose(); + if (onEditSuccess) { + onEditSuccess(); + } + }, + (projectUuid) => { + onClose(); + if (onApproveSuccess) { + onApproveSuccess(projectUuid); + } + }, + projectTypes, + applicationCreatedAt + ); + + // Inicializar formulario cuando se abre el modal + useEffect(() => { + if (isOpen && application) { + initializeForm(application); + } + }, [isOpen, application]); + + if (!isOpen) return null; + + // Agregar opción "Otro" a los tipos de problemática + const extendedProblemTypes = [...problemTypes, { problem_type_id: "otro", name: "Otro" }]; + + const handleFacultyChange = (facultyId) => { + handleChange({ + target: { + name: 'faculty', + value: facultyId, + type: 'radio', + checked: true + } + }); + }; + + return ( +
+
+ + {/* Header */} +
+

+ Editar proyecto +

+ +
+ + {/* Body */} +
+ + {/* Tipo de Proyecto */} +
+ +
+ {projectTypes.map((type) => ( + + ))} +
+ {fieldErrors.projectType && ( +

{fieldErrors.projectType}

+ )} +
+ + {/* Facultad */} +
+ +
+ {faculties.map((faculty) => ( + + ))} +
+ {fieldErrors.faculty && ( +

{fieldErrors.faculty}

+ )} +
+ + {/* Tipo de Problemática */} +
+ +
+ {extendedProblemTypes.map((type) => ( + + ))} +
+ {fieldErrors.problemType && ( +

{fieldErrors.problemType}

+ )} +
+ + {/* Campo "Otro" - Descripción personalizada */} + {form.problemType.includes("otro") && ( +
+ +