mirror of
https://github.com/umami-software/umami.git
synced 2026-02-13 17:15:37 +01:00
Combine WebsiteNav into SideNav when viewing a website.
Replace the separate 240px WebsiteNav sidebar with website navigation integrated into the main SideNav. When a websiteId is active, SideNav shows a back arrow, WebsiteSelect dropdown, and grouped nav items instead of the main links. Extract shared useWebsiteNavItems hook used by both SideNav and WebsiteNav (still used by MobileNav). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
84522b2475
commit
635317ecfb
5 changed files with 284 additions and 165 deletions
|
|
@ -14,15 +14,23 @@ import {
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import type { Key } from 'react';
|
import type { Key } from 'react';
|
||||||
import { IconLabel } from '@/components/common/IconLabel';
|
import { IconLabel } from '@/components/common/IconLabel';
|
||||||
import { useGlobalState, useMessages, useNavigation } from '@/components/hooks';
|
import { useGlobalState, useMessages, useNavigation, useWebsiteNavItems } from '@/components/hooks';
|
||||||
import { Globe, Grid2x2, LayoutDashboard, LinkIcon, PanelLeft } from '@/components/icons';
|
import {
|
||||||
|
ArrowLeft,
|
||||||
|
Globe,
|
||||||
|
Grid2x2,
|
||||||
|
LayoutDashboard,
|
||||||
|
LinkIcon,
|
||||||
|
PanelLeft,
|
||||||
|
} from '@/components/icons';
|
||||||
import { LanguageButton } from '@/components/input/LanguageButton';
|
import { LanguageButton } from '@/components/input/LanguageButton';
|
||||||
import { NavButton } from '@/components/input/NavButton';
|
import { NavButton } from '@/components/input/NavButton';
|
||||||
|
import { WebsiteSelect } from '@/components/input/WebsiteSelect';
|
||||||
import { Logo } from '@/components/svg';
|
import { Logo } from '@/components/svg';
|
||||||
|
|
||||||
export function SideNav(props: any) {
|
export function SideNav(props: any) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { pathname, renderUrl, websiteId, router } = useNavigation();
|
const { pathname, renderUrl, websiteId, teamId, router } = useNavigation();
|
||||||
const [isCollapsed, setIsCollapsed] = useGlobalState('sidenav-collapsed');
|
const [isCollapsed, setIsCollapsed] = useGlobalState('sidenav-collapsed');
|
||||||
|
|
||||||
const hasNav = !!(websiteId || pathname.startsWith('/admin') || pathname.includes('/settings'));
|
const hasNav = !!(websiteId || pathname.startsWith('/admin') || pathname.includes('/settings'));
|
||||||
|
|
@ -58,6 +66,10 @@ export function SideNav(props: any) {
|
||||||
router.push(id === 'user' ? '/websites' : `/teams/${id}/websites`);
|
router.push(id === 'user' ? '/websites' : `/teams/${id}/websites`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleWebsiteChange = (value: string) => {
|
||||||
|
router.push(renderUrl(`/websites/${value}`));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column
|
<Column
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -73,8 +85,13 @@ export function SideNav(props: any) {
|
||||||
transition: 'width 0.2s ease-in-out',
|
transition: 'width 0.2s ease-in-out',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Column>
|
<Column overflowY="auto" style={{ minHeight: 0 }}>
|
||||||
<Row alignItems="center" justifyContent="space-between" height="60px">
|
<Row
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="space-between"
|
||||||
|
height="60px"
|
||||||
|
style={{ flexShrink: 0 }}
|
||||||
|
>
|
||||||
<Row paddingX="3" alignItems="center" justifyContent="space-between" flexGrow={1}>
|
<Row paddingX="3" alignItems="center" justifyContent="space-between" flexGrow={1}>
|
||||||
{!isCollapsed && (
|
{!isCollapsed && (
|
||||||
<IconLabel icon={<Logo />}>
|
<IconLabel icon={<Logo />}>
|
||||||
|
|
@ -84,18 +101,120 @@ export function SideNav(props: any) {
|
||||||
<PanelButton />
|
<PanelButton />
|
||||||
</Row>
|
</Row>
|
||||||
</Row>
|
</Row>
|
||||||
<Row marginBottom="4">
|
<Row marginBottom="4" style={{ flexShrink: 0 }}>
|
||||||
<NavButton showText={!isCollapsed} onAction={handleSelect} />
|
<NavButton showText={!isCollapsed} onAction={handleSelect} />
|
||||||
</Row>
|
</Row>
|
||||||
<Column gap="2">
|
{websiteId ? (
|
||||||
{links.map(({ id, path, label, icon }) => {
|
<WebsiteNavSection
|
||||||
|
websiteId={websiteId}
|
||||||
|
teamId={teamId}
|
||||||
|
isCollapsed={isCollapsed}
|
||||||
|
onWebsiteChange={handleWebsiteChange}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Column gap="2">
|
||||||
|
{links.map(({ id, path, label, icon }) => {
|
||||||
|
return (
|
||||||
|
<Link key={id} href={renderUrl(path, false)} role="button">
|
||||||
|
<TooltipTrigger isDisabled={!isCollapsed} delay={0}>
|
||||||
|
<Focusable>
|
||||||
|
<Row
|
||||||
|
alignItems="center"
|
||||||
|
hover={{ backgroundColor: 'surface-sunken' }}
|
||||||
|
borderRadius
|
||||||
|
minHeight="40px"
|
||||||
|
>
|
||||||
|
<IconLabel icon={icon} label={isCollapsed ? '' : label} padding />
|
||||||
|
</Row>
|
||||||
|
</Focusable>
|
||||||
|
<Tooltip placement="right">{label}</Tooltip>
|
||||||
|
</TooltipTrigger>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Column>
|
||||||
|
)}
|
||||||
|
</Column>
|
||||||
|
<Row alignItems="center" justifyContent="center" wrap="wrap" marginBottom="4" gap>
|
||||||
|
<LanguageButton />
|
||||||
|
<ThemeButton />
|
||||||
|
</Row>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function WebsiteNavSection({
|
||||||
|
websiteId,
|
||||||
|
teamId,
|
||||||
|
isCollapsed,
|
||||||
|
onWebsiteChange,
|
||||||
|
}: {
|
||||||
|
websiteId: string;
|
||||||
|
teamId: string;
|
||||||
|
isCollapsed: boolean;
|
||||||
|
onWebsiteChange: (value: string) => void;
|
||||||
|
}) {
|
||||||
|
const { formatMessage, labels } = useMessages();
|
||||||
|
const { renderUrl } = useNavigation();
|
||||||
|
const { items, selectedKey } = useWebsiteNavItems(websiteId);
|
||||||
|
|
||||||
|
const renderValue = (value: any) => {
|
||||||
|
return (
|
||||||
|
<Text truncate style={{ maxWidth: 160, lineHeight: 1 }}>
|
||||||
|
{value?.selectedItem?.name}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column gap="2">
|
||||||
|
<Link href={renderUrl('/websites', false)} role="button">
|
||||||
|
<TooltipTrigger isDisabled={!isCollapsed} delay={0}>
|
||||||
|
<Focusable>
|
||||||
|
<Row
|
||||||
|
alignItems="center"
|
||||||
|
hover={{ backgroundColor: 'surface-sunken' }}
|
||||||
|
borderRadius
|
||||||
|
minHeight="40px"
|
||||||
|
>
|
||||||
|
<IconLabel
|
||||||
|
icon={<ArrowLeft />}
|
||||||
|
label={isCollapsed ? '' : formatMessage(labels.back)}
|
||||||
|
padding
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
</Focusable>
|
||||||
|
<Tooltip placement="right">{formatMessage(labels.back)}</Tooltip>
|
||||||
|
</TooltipTrigger>
|
||||||
|
</Link>
|
||||||
|
{!isCollapsed && (
|
||||||
|
<Box marginBottom="2">
|
||||||
|
<WebsiteSelect
|
||||||
|
websiteId={websiteId}
|
||||||
|
teamId={teamId}
|
||||||
|
onChange={onWebsiteChange}
|
||||||
|
renderValue={renderValue}
|
||||||
|
buttonProps={{ style: { outline: 'none' } }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{items.map(({ label: sectionLabel, items: sectionItems }, index) => (
|
||||||
|
<Column key={`${sectionLabel}${index}`} gap="1" marginBottom="1">
|
||||||
|
{!isCollapsed && (
|
||||||
|
<Row padding>
|
||||||
|
<Text weight="bold">{sectionLabel}</Text>
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
{sectionItems.map(({ id, path, label, icon }) => {
|
||||||
|
const isSelected = selectedKey === id;
|
||||||
return (
|
return (
|
||||||
<Link key={id} href={renderUrl(path, false)} role="button">
|
<Link key={id} href={path} role="button">
|
||||||
<TooltipTrigger isDisabled={!isCollapsed} delay={0}>
|
<TooltipTrigger isDisabled={!isCollapsed} delay={0}>
|
||||||
<Focusable>
|
<Focusable>
|
||||||
<Row
|
<Row
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
hover={{ backgroundColor: 'surface-sunken' }}
|
hover={{ backgroundColor: 'surface-sunken' }}
|
||||||
|
backgroundColor={isSelected ? 'surface-sunken' : undefined}
|
||||||
borderRadius
|
borderRadius
|
||||||
minHeight="40px"
|
minHeight="40px"
|
||||||
>
|
>
|
||||||
|
|
@ -108,11 +227,7 @@ export function SideNav(props: any) {
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Column>
|
</Column>
|
||||||
</Column>
|
))}
|
||||||
<Row alignItems="center" justifyContent="center" wrap="wrap" marginBottom="4" gap>
|
|
||||||
<LanguageButton />
|
|
||||||
<ThemeButton />
|
|
||||||
</Row>
|
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,17 @@
|
||||||
'use client';
|
'use client';
|
||||||
import { Column, Grid } from '@umami/react-zen';
|
import { Column } from '@umami/react-zen';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { WebsiteProvider } from '@/app/(main)/websites/WebsiteProvider';
|
import { WebsiteProvider } from '@/app/(main)/websites/WebsiteProvider';
|
||||||
import { PageBody } from '@/components/common/PageBody';
|
import { PageBody } from '@/components/common/PageBody';
|
||||||
import { WebsiteHeader } from './WebsiteHeader';
|
import { WebsiteHeader } from './WebsiteHeader';
|
||||||
import { WebsiteNav } from './WebsiteNav';
|
|
||||||
|
|
||||||
export function WebsiteLayout({ websiteId, children }: { websiteId: string; children: ReactNode }) {
|
export function WebsiteLayout({ websiteId, children }: { websiteId: string; children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<WebsiteProvider websiteId={websiteId}>
|
<WebsiteProvider websiteId={websiteId}>
|
||||||
<Grid columns={{ base: '1fr', lg: 'auto 1fr' }} width="100%">
|
<PageBody gap>
|
||||||
<Column display={{ base: 'none', lg: 'flex' }} width="240px" border="right" marginRight="2">
|
<WebsiteHeader showActions />
|
||||||
<WebsiteNav websiteId={websiteId} />
|
<Column>{children}</Column>
|
||||||
</Column>
|
</PageBody>
|
||||||
<PageBody gap>
|
|
||||||
<WebsiteHeader showActions />
|
|
||||||
<Column>{children}</Column>
|
|
||||||
</PageBody>
|
|
||||||
</Grid>
|
|
||||||
</WebsiteProvider>
|
</WebsiteProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,7 @@
|
||||||
import { Column, Text } from '@umami/react-zen';
|
import { Column, Text } from '@umami/react-zen';
|
||||||
import { NavMenu } from '@/components/common/NavMenu';
|
import { NavMenu } from '@/components/common/NavMenu';
|
||||||
import { useMessages, useNavigation } from '@/components/hooks';
|
import { useNavigation, useWebsiteNavItems } from '@/components/hooks';
|
||||||
import {
|
|
||||||
AlignEndHorizontal,
|
|
||||||
ChartPie,
|
|
||||||
Clock,
|
|
||||||
Eye,
|
|
||||||
Sheet,
|
|
||||||
Tag,
|
|
||||||
User,
|
|
||||||
UserPlus,
|
|
||||||
} from '@/components/icons';
|
|
||||||
import { WebsiteSelect } from '@/components/input/WebsiteSelect';
|
import { WebsiteSelect } from '@/components/input/WebsiteSelect';
|
||||||
import { Funnel, Lightning, Magnet, Money, Network, Path, Target } from '@/components/svg';
|
|
||||||
|
|
||||||
export function WebsiteNav({
|
export function WebsiteNav({
|
||||||
websiteId,
|
websiteId,
|
||||||
|
|
@ -21,130 +10,8 @@ export function WebsiteNav({
|
||||||
websiteId: string;
|
websiteId: string;
|
||||||
onItemClick?: () => void;
|
onItemClick?: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { teamId, router, renderUrl } = useNavigation();
|
||||||
const { pathname, renderUrl, teamId, router } = useNavigation();
|
const { items, selectedKey } = useWebsiteNavItems(websiteId);
|
||||||
|
|
||||||
const renderPath = (path: string) =>
|
|
||||||
renderUrl(`/websites/${websiteId}${path}`, {
|
|
||||||
event: undefined,
|
|
||||||
compare: undefined,
|
|
||||||
view: undefined,
|
|
||||||
unit: undefined,
|
|
||||||
excludeBounce: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const items = [
|
|
||||||
{
|
|
||||||
label: formatMessage(labels.traffic),
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
id: 'overview',
|
|
||||||
label: formatMessage(labels.overview),
|
|
||||||
icon: <Eye />,
|
|
||||||
path: renderPath(''),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'events',
|
|
||||||
label: formatMessage(labels.events),
|
|
||||||
icon: <Lightning />,
|
|
||||||
path: renderPath('/events'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'sessions',
|
|
||||||
label: formatMessage(labels.sessions),
|
|
||||||
icon: <User />,
|
|
||||||
path: renderPath('/sessions'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'realtime',
|
|
||||||
label: formatMessage(labels.realtime),
|
|
||||||
icon: <Clock />,
|
|
||||||
path: renderPath('/realtime'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'compare',
|
|
||||||
label: formatMessage(labels.compare),
|
|
||||||
icon: <AlignEndHorizontal />,
|
|
||||||
path: renderPath('/compare'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'breakdown',
|
|
||||||
label: formatMessage(labels.breakdown),
|
|
||||||
icon: <Sheet />,
|
|
||||||
path: renderPath('/breakdown'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: formatMessage(labels.behavior),
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
id: 'goals',
|
|
||||||
label: formatMessage(labels.goals),
|
|
||||||
icon: <Target />,
|
|
||||||
path: renderPath('/goals'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'funnel',
|
|
||||||
label: formatMessage(labels.funnels),
|
|
||||||
icon: <Funnel />,
|
|
||||||
path: renderPath('/funnels'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'journeys',
|
|
||||||
label: formatMessage(labels.journeys),
|
|
||||||
icon: <Path />,
|
|
||||||
path: renderPath('/journeys'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'retention',
|
|
||||||
label: formatMessage(labels.retention),
|
|
||||||
icon: <Magnet />,
|
|
||||||
path: renderPath('/retention'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: formatMessage(labels.audience),
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
id: 'segments',
|
|
||||||
label: formatMessage(labels.segments),
|
|
||||||
icon: <ChartPie />,
|
|
||||||
path: renderPath('/segments'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'cohorts',
|
|
||||||
label: formatMessage(labels.cohorts),
|
|
||||||
icon: <UserPlus />,
|
|
||||||
path: renderPath('/cohorts'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: formatMessage(labels.growth),
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
id: 'utm',
|
|
||||||
label: formatMessage(labels.utm),
|
|
||||||
icon: <Tag />,
|
|
||||||
path: renderPath('/utm'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'revenue',
|
|
||||||
label: formatMessage(labels.revenue),
|
|
||||||
icon: <Money />,
|
|
||||||
path: renderPath('/revenue'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'attribution',
|
|
||||||
label: formatMessage(labels.attribution),
|
|
||||||
icon: <Network />,
|
|
||||||
path: renderPath('/attribution'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const handleChange = (value: string) => {
|
const handleChange = (value: string) => {
|
||||||
router.push(renderUrl(`/websites/${value}`));
|
router.push(renderUrl(`/websites/${value}`));
|
||||||
|
|
@ -158,10 +25,6 @@ export function WebsiteNav({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectedKey = items
|
|
||||||
.flatMap(e => e.items)
|
|
||||||
.find(({ path }) => path && pathname.endsWith(path.split('?')[0]))?.id;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column padding="2" position="sticky" top="0" gap>
|
<Column padding="2" position="sticky" top="0" gap>
|
||||||
<WebsiteSelect
|
<WebsiteSelect
|
||||||
|
|
|
||||||
|
|
@ -87,3 +87,4 @@ export * from './useRegionNames';
|
||||||
export * from './useSlug';
|
export * from './useSlug';
|
||||||
export * from './useSticky';
|
export * from './useSticky';
|
||||||
export * from './useTimezone';
|
export * from './useTimezone';
|
||||||
|
export * from './useWebsiteNavItems';
|
||||||
|
|
|
||||||
146
src/components/hooks/useWebsiteNavItems.tsx
Normal file
146
src/components/hooks/useWebsiteNavItems.tsx
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
import {
|
||||||
|
AlignEndHorizontal,
|
||||||
|
ChartPie,
|
||||||
|
Clock,
|
||||||
|
Eye,
|
||||||
|
Sheet,
|
||||||
|
Tag,
|
||||||
|
User,
|
||||||
|
UserPlus,
|
||||||
|
} from '@/components/icons';
|
||||||
|
import { Funnel, Lightning, Magnet, Money, Network, Path, Target } from '@/components/svg';
|
||||||
|
import { useMessages } from './useMessages';
|
||||||
|
import { useNavigation } from './useNavigation';
|
||||||
|
|
||||||
|
export function useWebsiteNavItems(websiteId: string) {
|
||||||
|
const { formatMessage, labels } = useMessages();
|
||||||
|
const { pathname, renderUrl } = useNavigation();
|
||||||
|
|
||||||
|
const renderPath = (path: string) =>
|
||||||
|
renderUrl(`/websites/${websiteId}${path}`, {
|
||||||
|
event: undefined,
|
||||||
|
compare: undefined,
|
||||||
|
view: undefined,
|
||||||
|
unit: undefined,
|
||||||
|
excludeBounce: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{
|
||||||
|
label: formatMessage(labels.traffic),
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'overview',
|
||||||
|
label: formatMessage(labels.overview),
|
||||||
|
icon: <Eye />,
|
||||||
|
path: renderPath(''),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'events',
|
||||||
|
label: formatMessage(labels.events),
|
||||||
|
icon: <Lightning />,
|
||||||
|
path: renderPath('/events'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sessions',
|
||||||
|
label: formatMessage(labels.sessions),
|
||||||
|
icon: <User />,
|
||||||
|
path: renderPath('/sessions'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'realtime',
|
||||||
|
label: formatMessage(labels.realtime),
|
||||||
|
icon: <Clock />,
|
||||||
|
path: renderPath('/realtime'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'compare',
|
||||||
|
label: formatMessage(labels.compare),
|
||||||
|
icon: <AlignEndHorizontal />,
|
||||||
|
path: renderPath('/compare'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'breakdown',
|
||||||
|
label: formatMessage(labels.breakdown),
|
||||||
|
icon: <Sheet />,
|
||||||
|
path: renderPath('/breakdown'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: formatMessage(labels.behavior),
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'goals',
|
||||||
|
label: formatMessage(labels.goals),
|
||||||
|
icon: <Target />,
|
||||||
|
path: renderPath('/goals'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'funnel',
|
||||||
|
label: formatMessage(labels.funnels),
|
||||||
|
icon: <Funnel />,
|
||||||
|
path: renderPath('/funnels'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'journeys',
|
||||||
|
label: formatMessage(labels.journeys),
|
||||||
|
icon: <Path />,
|
||||||
|
path: renderPath('/journeys'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'retention',
|
||||||
|
label: formatMessage(labels.retention),
|
||||||
|
icon: <Magnet />,
|
||||||
|
path: renderPath('/retention'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: formatMessage(labels.audience),
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'segments',
|
||||||
|
label: formatMessage(labels.segments),
|
||||||
|
icon: <ChartPie />,
|
||||||
|
path: renderPath('/segments'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cohorts',
|
||||||
|
label: formatMessage(labels.cohorts),
|
||||||
|
icon: <UserPlus />,
|
||||||
|
path: renderPath('/cohorts'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: formatMessage(labels.growth),
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'utm',
|
||||||
|
label: formatMessage(labels.utm),
|
||||||
|
icon: <Tag />,
|
||||||
|
path: renderPath('/utm'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'revenue',
|
||||||
|
label: formatMessage(labels.revenue),
|
||||||
|
icon: <Money />,
|
||||||
|
path: renderPath('/revenue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'attribution',
|
||||||
|
label: formatMessage(labels.attribution),
|
||||||
|
icon: <Network />,
|
||||||
|
path: renderPath('/attribution'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const selectedKey = items
|
||||||
|
.flatMap(e => e.items)
|
||||||
|
.find(({ path }) => path && pathname.endsWith(path.split('?')[0]))?.id;
|
||||||
|
|
||||||
|
return { items, selectedKey, renderPath };
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue