diff --git a/frontend/src/Router.tsx b/frontend/src/Router.tsx index 17c75e2..e4f3c49 100644 --- a/frontend/src/Router.tsx +++ b/frontend/src/Router.tsx @@ -17,6 +17,9 @@ const router = createBrowserRouter([ path: '/Grid_Dashboard', element: , }, + + + { path: '*', element: , diff --git a/frontend/src/components/Appshell/Appshell.module.css b/frontend/src/components/Appshell/Appshell.module.css index bce8086..1787f43 100644 --- a/frontend/src/components/Appshell/Appshell.module.css +++ b/frontend/src/components/Appshell/Appshell.module.css @@ -97,7 +97,7 @@ &, &:hover { background-color: var(--mantine-color-brand-7); - color: linear-gradient(90deg, var(--mantine-color-brand-7), var(--mantine-color-brand-4)); + color: var(--mantine-color-brand-2); } } } \ No newline at end of file diff --git a/frontend/src/components/Appshell/Appshell.tsx b/frontend/src/components/Appshell/Appshell.tsx index bd0d0f5..b5776c9 100644 --- a/frontend/src/components/Appshell/Appshell.tsx +++ b/frontend/src/components/Appshell/Appshell.tsx @@ -2,149 +2,190 @@ import { AppShell, Burger, Group, - Skeleton, Tooltip, UnstyledButton, - ActionIcon, Title, + useMantineTheme, } from '@mantine/core'; +import { useDisclosure, useMediaQuery } from '@mantine/hooks'; +import { useEffect, useMemo, useRef, useState } from 'react'; +import { Link, useLocation, useNavigate } from 'react-router-dom'; - import { default as LogoIcon } from '../../assets/icons/favicon'; +import { default as LogoIcon } from '../../assets/icons/favicon'; +import { mainLinksdata } from '../../data/navigationsLinks_1'; +import { submenuLinks } from '../../data/submenuLinks_1'; - import { useMantineTheme } from '@mantine/core'; +import classes from './Appshell.module.css'; - import { mainLinksdata } from '../../data/navigationsLinks_1'; - import { submenuLinks } from '../../data/submenuLinks_1'; - - import { useDisclosure, useMediaQuery } from '@mantine/hooks'; - import { useEffect, useMemo, useState } from 'react'; - import { Link, useLocation } from 'react-router-dom'; - import classes from './Appshell.module.css'; - - +type AppShellWithMenuProps = { + children?: React.ReactNode; +}; - type AppShellWithMenuProps = { - children?: React.ReactNode; // <- ahora es opcional - }; +// Persistencia en localStorage +const STORAGE_KEY = 'lastSubmenuRoutes'; - export function AppShellWithMenu({ children }: AppShellWithMenuProps) { - - const theme = useMantineTheme(); - - - const location = useLocation(); - const isMobile = useMediaQuery('(max-width: 768px)'); - const [mobileOpened, { toggle: toggleMobile, close: closeMobile }] = useDisclosure(false); - const [desktopOpened, { toggle: toggleDesktop, open: openDesktop }] = useDisclosure(true); - const isCollapsed = useMemo(() => (isMobile ? !mobileOpened : !desktopOpened), [isMobile, mobileOpened, desktopOpened]); - - const [manualActiveTab, setManualActiveTab] = useState(null); - - const matchedMain = Object.entries(submenuLinks).find(([mainKey, items]) => - items.some((item) => location.pathname.startsWith(item.to)) - ); - - const routeBasedActive = matchedMain?.[0] ?? 'Home'; - const active = manualActiveTab ?? routeBasedActive; - const activeLink = submenuLinks[active as keyof typeof submenuLinks]?.find((item) => location.pathname === item.to)?.label ?? ''; - - const mainLinks = mainLinksdata.map((link) => ( - - { - setManualActiveTab(link.label); - - }} - className={classes.mainLink} - data-active={link.label === active || undefined} - > - - - - )); - - const links = (submenuLinks[active as keyof typeof submenuLinks] || []).map((item) => ( - { - if (isMobile) closeMobile(); - }} - > - {item.label} - - )); - - useEffect(() => { - setManualActiveTab(null); - }, [location.pathname]); - - useEffect(() => { - if (!isMobile) openDesktop(); - }, [isMobile, openDesktop]); - - return ( - - - {/* Header */} - - - - - - - - - - - - {/* Navbar */} - - -
-
-
- - {mainLinks} -
- -
-
- {!isCollapsed && {active}} - {links} -
-
-
- - {/* Main Content */} - - - {children} - - - -
- ); +function getLastSubmenuRoute(section: string): string | null { + try { + const raw = localStorage.getItem(STORAGE_KEY); + const parsed = raw ? JSON.parse(raw) : {}; + return parsed[section] ?? null; + } catch { + return null; } - \ No newline at end of file +} + +function setLastSubmenuRoute(section: string, route: string) { + try { + const raw = localStorage.getItem(STORAGE_KEY); + const parsed = raw ? JSON.parse(raw) : {}; + parsed[section] = route; + localStorage.setItem(STORAGE_KEY, JSON.stringify(parsed)); + } catch { + // fallback silencioso + } +} + +export function AppShellWithMenu({ children }: AppShellWithMenuProps) { + const theme = useMantineTheme(); + const location = useLocation(); + const navigate = useNavigate(); + + const isMobile = useMediaQuery('(max-width: 768px)'); + const [mobileOpened, { toggle: toggleMobile, close: closeMobile }] = useDisclosure(false); + const [desktopOpened, { toggle: toggleDesktop, open: openDesktop }] = useDisclosure(true); + + const isCollapsed = useMemo( + () => (isMobile ? !mobileOpened : !desktopOpened), + [isMobile, mobileOpened, desktopOpened] + ); + + // Estado para el main link activo + const [activeMain, setActiveMain] = useState('Home'); + + // Ref para saber si el usuario ha hecho clic manualmente en el main link + const userClickedMainRef = useRef(false); + + useEffect(() => { + const currentPath = location.pathname.toLowerCase().replace(/\/$/, ''); + + let matchedMain: string | null = null; + let maxMatchLength = 0; + + Object.entries(submenuLinks).forEach(([main, items]) => { + items.forEach((item) => { + const itemPath = item.to.toLowerCase().replace(/\/$/, ''); + if ( + currentPath === itemPath || + currentPath.startsWith(itemPath + '/') + ) { + if (itemPath.length > maxMatchLength) { + matchedMain = main; + maxMatchLength = itemPath.length; + } + } + }); + }); + + if (matchedMain) { + setActiveMain(matchedMain); + } + }, [location.pathname]); + + const activeLink = + submenuLinks[activeMain as keyof typeof submenuLinks]?.find( + (item) => item.to === location.pathname + )?.label ?? ''; + + const mainLinks = mainLinksdata.map((link) => ( + + { + userClickedMainRef.current = true; + setActiveMain(link.label); + + const remembered = getLastSubmenuRoute(link.label); + const fallback = submenuLinks[link.label as keyof typeof submenuLinks]?.[0]?.to; + + if (isCollapsed && (remembered || fallback)) { + navigate(remembered ?? fallback); + } + }} + className={classes.mainLink} + data-active={link.label === activeMain || undefined} + > + + + + )); + + const links = (submenuLinks[activeMain as keyof typeof submenuLinks] || []).map((item) => ( + { + setLastSubmenuRoute(activeMain, item.to); + if (isMobile) closeMobile(); + }} + > + {item.label} + + )); + + useEffect(() => { + if (!isMobile) openDesktop(); + }, [isMobile, openDesktop]); + + return ( + + {/* Header */} + + + + + + + + + {/* Navbar */} + +
+
+
{mainLinks}
+
+
+ {!isCollapsed && ( + + {activeMain} + + )} + {links} +
+
+
+ + {/* Main Content */} + {children} +
+ ); +} diff --git a/frontend/src/components/Welcome/Welcome.tsx b/frontend/src/components/Welcome/Welcome.tsx index 8ca8a97..366048c 100644 --- a/frontend/src/components/Welcome/Welcome.tsx +++ b/frontend/src/components/Welcome/Welcome.tsx @@ -11,7 +11,7 @@ export function Welcome() { Hola! {' '} <Text inherit variant="gradient" component="span" gradient={{ from: theme.colors.brand[7], to: theme.colors.secondary[4], }} style={{ letterSpacing: '1px' }}> - Egutierrez + Holooooo </Text> diff --git a/frontend/src/data/submenuLinks_1.ts b/frontend/src/data/submenuLinks_1.ts index d653bdc..8142585 100644 --- a/frontend/src/data/submenuLinks_1.ts +++ b/frontend/src/data/submenuLinks_1.ts @@ -4,10 +4,11 @@ export const submenuLinks = { Home: [ { label: 'Inicio', to: '/' }, { label: 'Consulta Api', to: '/Consulta_API' }, - { label: 'Grid_Dashboard', to: '/Grid_Dashboard' }, + ], Dashboard: [ { label: 'Resumen', to: '/dashboard/resumen' }, + { label: 'Grid_Dashboard', to: '/Grid_Dashboard' }, { label: 'Estadísticas', to: '/dashboard/estadisticas' }, { label: 'Usuarios', to: '/dashboard/usuarios' }, ],