27f71a05f3
- Introduced a new page for API consultation (`Consulta_API`) with a form to send requests. - Added routing for the new page and a 404 error page. - Created a `MetodoSelect` component for selecting HTTP methods. - Implemented a `MiBoton` component for a customizable button. - Updated the `HomePage` layout to include a sidebar (`DoubleNavbar`) and main content area. - Enhanced the `Welcome` component with a new greeting and description. - Added a holographic shader background to the 404 error page. - Updated dependencies in `yarn.lock` for new components and features. - Styled the sidebar and main content for better user experience.
155 lines
4.5 KiB
TypeScript
155 lines
4.5 KiB
TypeScript
import {
|
|
IconCalendarStats,
|
|
IconDeviceDesktopAnalytics,
|
|
IconFingerprint,
|
|
IconGauge,
|
|
IconHome2,
|
|
IconSettings,
|
|
IconUser,
|
|
IconArrowBarLeft,
|
|
IconArrowBarRight,
|
|
} from '@tabler/icons-react';
|
|
import {
|
|
Title,
|
|
Tooltip,
|
|
UnstyledButton,
|
|
ActionIcon,
|
|
} from '@mantine/core';
|
|
import { Link, useLocation } from 'react-router-dom';
|
|
import { useEffect, useState } from 'react';
|
|
import classes from './DoubleNavbar.module.css';
|
|
|
|
const mainLinksdata = [
|
|
{ icon: IconHome2, label: 'Home' },
|
|
{ icon: IconGauge, label: 'Dashboard' },
|
|
{ icon: IconDeviceDesktopAnalytics, label: 'Analytics' },
|
|
{ icon: IconCalendarStats, label: 'Releases' },
|
|
{ icon: IconUser, label: 'Account' },
|
|
{ icon: IconFingerprint, label: 'Security' },
|
|
{ icon: IconSettings, label: 'Settings' },
|
|
];
|
|
|
|
const submenuLinks: Record<string, { label: string; to: string }[]> = {
|
|
Home: [
|
|
{ label: 'Inicio', to: '/' },
|
|
{ label: 'Consulta Api', to: '/Consulta_API' },
|
|
],
|
|
Dashboard: [
|
|
{ label: 'Resumen', to: '/dashboard/resumen' },
|
|
{ label: 'Estadísticas', to: '/dashboard/estadisticas' },
|
|
{ label: 'Usuarios', to: '/dashboard/usuarios' },
|
|
],
|
|
Analytics: [
|
|
{ label: 'Conversiones', to: '/analytics/conversiones' },
|
|
{ label: 'Tráfico', to: '/analytics/trafico' },
|
|
{ label: 'Tendencias', to: '/analytics/tendencias' },
|
|
],
|
|
Releases: [
|
|
{ label: 'Notas de versión', to: '/releases/notas-de-version' },
|
|
{ label: 'Historial', to: '/releases/historial' },
|
|
],
|
|
Account: [
|
|
{ label: 'Perfil', to: '/account/perfil' },
|
|
{ label: 'Suscripciones', to: '/account/suscripciones' },
|
|
],
|
|
Security: [
|
|
{ label: 'Contraseña', to: '/security/contraseña' },
|
|
{ label: '2FA', to: '/security/2fa' },
|
|
],
|
|
Settings: [
|
|
{ label: 'Preferencias', to: '/settings/preferencias' },
|
|
{ label: 'Notificaciones', to: '/settings/notificaciones' },
|
|
],
|
|
};
|
|
|
|
export function DoubleNavbar() {
|
|
const location = useLocation();
|
|
const [collapsed, setCollapsed] = useState(false);
|
|
const [manualActiveTab, setManualActiveTab] = useState<string | null>(null);
|
|
|
|
// Detectar cuál pestaña es activa por la ruta actual
|
|
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]?.find((item) => location.pathname === item.to)?.label ?? '';
|
|
|
|
const mainLinks = mainLinksdata.map((link) => (
|
|
<Tooltip
|
|
label={link.label}
|
|
position="right"
|
|
withArrow
|
|
transitionProps={{ duration: 0 }}
|
|
key={link.label}
|
|
>
|
|
<UnstyledButton
|
|
onClick={() => setManualActiveTab(link.label)}
|
|
className={classes.mainLink}
|
|
data-active={link.label === active || undefined}
|
|
>
|
|
<link.icon size={22} stroke={1.5} />
|
|
</UnstyledButton>
|
|
</Tooltip>
|
|
));
|
|
|
|
const links = (submenuLinks[active] || []).map((item) => (
|
|
<Link
|
|
className={classes.link}
|
|
data-active={activeLink === item.label || undefined}
|
|
to={item.to}
|
|
key={item.label}
|
|
style={{ display: collapsed ? 'none' : 'block' }}
|
|
>
|
|
{item.label}
|
|
</Link>
|
|
));
|
|
|
|
// Resetea pestaña seleccionada si la ruta cambia (opcional)
|
|
useEffect(() => {
|
|
setManualActiveTab(null);
|
|
}, [location.pathname]);
|
|
|
|
return (
|
|
<nav className={`${classes.navbar} ${collapsed ? classes.collapsed : ''}`}>
|
|
<div className={classes.wrapper}>
|
|
<div className={classes.aside}>
|
|
{/* Sección superior: logo + mainLinks */}
|
|
<div className={classes.topSection}>
|
|
<div className={classes.logo}>
|
|
<img
|
|
src="/src/favicon.svg"
|
|
alt="Logo"
|
|
style={{ width: 30, height: 30 }}
|
|
/>
|
|
</div>
|
|
|
|
{mainLinks}
|
|
</div>
|
|
|
|
{/* Botón de colapsar separado abajo */}
|
|
<ActionIcon
|
|
variant="subtle"
|
|
onClick={() => setCollapsed((c) => !c)}
|
|
className={classes.collapseButton}
|
|
size="lg"
|
|
>
|
|
{collapsed ? <IconArrowBarRight size={20} /> : <IconArrowBarLeft size={20} />}
|
|
</ActionIcon>
|
|
</div>
|
|
|
|
<div className={classes.main}>
|
|
{!collapsed && (
|
|
<Title order={4} className={classes.title}>
|
|
{active}
|
|
</Title>
|
|
)}
|
|
{links}
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
);
|
|
} |