feat: Update routing and submenu links for Grid_Dashboard; modify Welcome component text and Appshell styles

This commit is contained in:
2025-05-09 00:22:47 +02:00
parent 8d35da1972
commit 2becc8bc7c
5 changed files with 184 additions and 139 deletions
+3
View File
@@ -17,6 +17,9 @@ const router = createBrowserRouter([
path: '/Grid_Dashboard', path: '/Grid_Dashboard',
element: <Grid_Dashboard />, element: <Grid_Dashboard />,
}, },
{ {
path: '*', path: '*',
element: <Error_404 />, element: <Error_404 />,
@@ -97,7 +97,7 @@
&, &,
&:hover { &:hover {
background-color: var(--mantine-color-brand-7); 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);
} }
} }
} }
+88 -47
View File
@@ -2,51 +2,99 @@ import {
AppShell, AppShell,
Burger, Burger,
Group, Group,
Skeleton,
Tooltip, Tooltip,
UnstyledButton, UnstyledButton,
ActionIcon,
Title, Title,
useMantineTheme,
} from '@mantine/core'; } 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 { useMantineTheme } from '@mantine/core';
import { mainLinksdata } from '../../data/navigationsLinks_1'; import { mainLinksdata } from '../../data/navigationsLinks_1';
import { submenuLinks } from '../../data/submenuLinks_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'; import classes from './Appshell.module.css';
type AppShellWithMenuProps = { type AppShellWithMenuProps = {
children?: React.ReactNode; // <- ahora es opcional children?: React.ReactNode;
}; };
// Persistencia en localStorage
const STORAGE_KEY = 'lastSubmenuRoutes';
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;
}
}
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) { export function AppShellWithMenu({ children }: AppShellWithMenuProps) {
const theme = useMantineTheme(); const theme = useMantineTheme();
const location = useLocation(); const location = useLocation();
const navigate = useNavigate();
const isMobile = useMediaQuery('(max-width: 768px)'); const isMobile = useMediaQuery('(max-width: 768px)');
const [mobileOpened, { toggle: toggleMobile, close: closeMobile }] = useDisclosure(false); const [mobileOpened, { toggle: toggleMobile, close: closeMobile }] = useDisclosure(false);
const [desktopOpened, { toggle: toggleDesktop, open: openDesktop }] = useDisclosure(true); const [desktopOpened, { toggle: toggleDesktop, open: openDesktop }] = useDisclosure(true);
const isCollapsed = useMemo(() => (isMobile ? !mobileOpened : !desktopOpened), [isMobile, mobileOpened, desktopOpened]);
const [manualActiveTab, setManualActiveTab] = useState<string | null>(null); const isCollapsed = useMemo(
() => (isMobile ? !mobileOpened : !desktopOpened),
const matchedMain = Object.entries(submenuLinks).find(([mainKey, items]) => [isMobile, mobileOpened, desktopOpened]
items.some((item) => location.pathname.startsWith(item.to))
); );
const routeBasedActive = matchedMain?.[0] ?? 'Home'; // Estado para el main link activo
const active = manualActiveTab ?? routeBasedActive; const [activeMain, setActiveMain] = useState<string>('Home');
const activeLink = submenuLinks[active as keyof typeof submenuLinks]?.find((item) => location.pathname === item.to)?.label ?? '';
// 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) => ( const mainLinks = mainLinksdata.map((link) => (
<Tooltip <Tooltip
@@ -58,18 +106,25 @@ import {
> >
<UnstyledButton <UnstyledButton
onClick={() => { onClick={() => {
setManualActiveTab(link.label); 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} className={classes.mainLink}
data-active={link.label === active || undefined} data-active={link.label === activeMain || undefined}
> >
<link.icon /> <link.icon />
</UnstyledButton> </UnstyledButton>
</Tooltip> </Tooltip>
)); ));
const links = (submenuLinks[active as keyof typeof submenuLinks] || []).map((item) => ( const links = (submenuLinks[activeMain as keyof typeof submenuLinks] || []).map((item) => (
<Link <Link
className={classes.link} className={classes.link}
data-active={activeLink === item.label || undefined} data-active={activeLink === item.label || undefined}
@@ -77,6 +132,7 @@ import {
key={item.label} key={item.label}
style={{ display: isCollapsed ? 'none' : 'block' }} style={{ display: isCollapsed ? 'none' : 'block' }}
onClick={() => { onClick={() => {
setLastSubmenuRoute(activeMain, item.to);
if (isMobile) closeMobile(); if (isMobile) closeMobile();
}} }}
> >
@@ -84,10 +140,6 @@ import {
</Link> </Link>
)); ));
useEffect(() => {
setManualActiveTab(null);
}, [location.pathname]);
useEffect(() => { useEffect(() => {
if (!isMobile) openDesktop(); if (!isMobile) openDesktop();
}, [isMobile, openDesktop]); }, [isMobile, openDesktop]);
@@ -102,9 +154,7 @@ import {
}} }}
padding="md" padding="md"
> >
{/* Header */} {/* Header */}
<AppShell.Header> <AppShell.Header>
<Group h="100%" px="sm"> <Group h="100%" px="sm">
<Burger opened={mobileOpened} onClick={toggleMobile} hiddenFrom="sm" size="sm" /> <Burger opened={mobileOpened} onClick={toggleMobile} hiddenFrom="sm" size="sm" />
@@ -114,37 +164,28 @@ import {
circleFill={theme.colors.brand[9]} circleFill={theme.colors.brand[9]}
pathFill={theme.colors.secondary[2]} pathFill={theme.colors.secondary[2]}
/> />
</Group> </Group>
</AppShell.Header> </AppShell.Header>
{/* Navbar */} {/* Navbar */}
<AppShell.Navbar> <AppShell.Navbar>
<div className={classes.wrapper}> <div className={classes.wrapper}>
<div className={classes.aside}> <div className={classes.aside}>
<div className={classes.topSection}> <div className={classes.topSection}>{mainLinks}</div>
{mainLinks}
</div>
</div> </div>
<div className={classes.main}> <div className={classes.main}>
{!isCollapsed && <Title order={4} className={classes.title}>{active}</Title>} {!isCollapsed && (
<Title order={4} className={classes.title}>
{activeMain}
</Title>
)}
{links} {links}
</div> </div>
</div> </div>
</AppShell.Navbar> </AppShell.Navbar>
{/* Main Content */} {/* Main Content */}
<AppShell.Main>{children}</AppShell.Main>
<AppShell.Main>
{children}
</AppShell.Main>
</AppShell> </AppShell>
); );
} }
+1 -1
View File
@@ -11,7 +11,7 @@ export function Welcome() {
<Title className={classes.title} ta="center" mt={100}> <Title className={classes.title} ta="center" mt={100}>
Hola! {' '} Hola! {' '}
<Text inherit variant="gradient" component="span" gradient={{ from: theme.colors.brand[7], to: theme.colors.secondary[4], }} style={{ letterSpacing: '1px' }}> <Text inherit variant="gradient" component="span" gradient={{ from: theme.colors.brand[7], to: theme.colors.secondary[4], }} style={{ letterSpacing: '1px' }}>
Egutierrez Holooooo
</Text> </Text>
</Title> </Title>
<Text c="dimmed" ta="left" size="lg" maw={580} mx="auto" mt="xl"> <Text c="dimmed" ta="left" size="lg" maw={580} mx="auto" mt="xl">
+2 -1
View File
@@ -4,10 +4,11 @@ export const submenuLinks = {
Home: [ Home: [
{ label: 'Inicio', to: '/' }, { label: 'Inicio', to: '/' },
{ label: 'Consulta Api', to: '/Consulta_API' }, { label: 'Consulta Api', to: '/Consulta_API' },
{ label: 'Grid_Dashboard', to: '/Grid_Dashboard' },
], ],
Dashboard: [ Dashboard: [
{ label: 'Resumen', to: '/dashboard/resumen' }, { label: 'Resumen', to: '/dashboard/resumen' },
{ label: 'Grid_Dashboard', to: '/Grid_Dashboard' },
{ label: 'Estadísticas', to: '/dashboard/estadisticas' }, { label: 'Estadísticas', to: '/dashboard/estadisticas' },
{ label: 'Usuarios', to: '/dashboard/usuarios' }, { label: 'Usuarios', to: '/dashboard/usuarios' },
], ],