mirror of
https://github.com/umami-software/umami.git
synced 2025-12-06 01:18:00 +01:00
Refactored website components. New layout.
This commit is contained in:
parent
6e41ba2e2c
commit
06f76dda13
35 changed files with 1159 additions and 987 deletions
|
|
@ -78,7 +78,7 @@
|
||||||
"@react-spring/web": "^9.7.3",
|
"@react-spring/web": "^9.7.3",
|
||||||
"@svgr/cli": "^8.1.0",
|
"@svgr/cli": "^8.1.0",
|
||||||
"@tanstack/react-query": "^5.28.6",
|
"@tanstack/react-query": "^5.28.6",
|
||||||
"@umami/react-zen": "^0.108.0",
|
"@umami/react-zen": "^0.111.0",
|
||||||
"@umami/redis-client": "^0.27.0",
|
"@umami/redis-client": "^0.27.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"chalk": "^4.1.1",
|
"chalk": "^4.1.1",
|
||||||
|
|
|
||||||
1698
pnpm-lock.yaml
generated
1698
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,11 +1,10 @@
|
||||||
'use client';
|
'use client';
|
||||||
import { Grid, Loading, Column } from '@umami/react-zen';
|
import { Grid, Loading, Column, Row } from '@umami/react-zen';
|
||||||
import Script from 'next/script';
|
import Script from 'next/script';
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
import { UpdateNotice } from './UpdateNotice';
|
import { UpdateNotice } from './UpdateNotice';
|
||||||
import { SideNav } from '@/app/(main)/SideNav';
|
import { SideNav } from '@/app/(main)/SideNav';
|
||||||
import { MenuBar } from '@/app/(main)/MenuBar';
|
import { MenuBar } from '@/app/(main)/MenuBar';
|
||||||
import { Page } from '@/components/common/Page';
|
|
||||||
import { useLoginQuery, useConfig } from '@/components/hooks';
|
import { useLoginQuery, useConfig } from '@/components/hooks';
|
||||||
|
|
||||||
export function App({ children }) {
|
export function App({ children }) {
|
||||||
|
|
@ -31,25 +30,26 @@ export function App({ children }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid height="100vh" width="100%" columns="auto 1fr" rows="auto 1fr">
|
<Grid height="100vh" width="100%" columns="auto 1fr" rows="auto 1fr" backgroundColor="2">
|
||||||
<SideNav gridColumn="1 / 2" gridRow="1 / 3" />
|
<Column gridColumn="1 / 2" gridRow="1 / 3" backgroundColor>
|
||||||
<MenuBar gridColumn="2 / 3" gridRow="1 / 2" />
|
<SideNav />
|
||||||
|
</Column>
|
||||||
|
<Row gridColumn="2 / 3" gridRow="1 / 2">
|
||||||
|
<MenuBar />
|
||||||
|
</Row>
|
||||||
<Column
|
<Column
|
||||||
gridColumn="2 / 3"
|
gridColumn="2 / 3"
|
||||||
gridRow="2 / 3"
|
gridRow="2 / 3"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
overflow="auto"
|
overflow="auto"
|
||||||
backgroundColor="2"
|
|
||||||
position="relative"
|
position="relative"
|
||||||
>
|
>
|
||||||
<Page>
|
|
||||||
{children}
|
{children}
|
||||||
|
</Column>
|
||||||
|
<UpdateNotice user={user} config={config} />
|
||||||
{process.env.NODE_ENV === 'production' && !pathname.includes('/share/') && (
|
{process.env.NODE_ENV === 'production' && !pathname.includes('/share/') && (
|
||||||
<Script src={`${process.env.basePath || ''}/telemetry.js`} />
|
<Script src={`${process.env.basePath || ''}/telemetry.js`} />
|
||||||
)}
|
)}
|
||||||
</Page>
|
|
||||||
</Column>
|
|
||||||
<UpdateNotice user={user} config={config} />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,12 @@ import { ThemeButton, Row, Button, Icon } from '@umami/react-zen';
|
||||||
import { LanguageButton } from '@/components/input/LanguageButton';
|
import { LanguageButton } from '@/components/input/LanguageButton';
|
||||||
import { ProfileButton } from '@/components/input/ProfileButton';
|
import { ProfileButton } from '@/components/input/ProfileButton';
|
||||||
import { TeamsButton } from '@/components/input/TeamsButton';
|
import { TeamsButton } from '@/components/input/TeamsButton';
|
||||||
import type { RowProps } from '@umami/react-zen/Row';
|
|
||||||
import useGlobalState from '@/components/hooks/useGlobalState';
|
import useGlobalState from '@/components/hooks/useGlobalState';
|
||||||
import { Lucide } from '@/components/icons';
|
import { Lucide } from '@/components/icons';
|
||||||
import { WebsiteSelect } from '@/components/input/WebsiteSelect';
|
import { WebsiteSelect } from '@/components/input/WebsiteSelect';
|
||||||
import { useNavigation } from '@/components/hooks';
|
import { useNavigation } from '@/components/hooks';
|
||||||
|
|
||||||
export function MenuBar(props: RowProps) {
|
export function MenuBar() {
|
||||||
const [isCollapsed, setCollapsed] = useGlobalState('sidenav-collapsed');
|
const [isCollapsed, setCollapsed] = useGlobalState('sidenav-collapsed');
|
||||||
const { websiteId } = useNavigation();
|
const { websiteId } = useNavigation();
|
||||||
|
|
||||||
|
|
@ -16,14 +15,13 @@ export function MenuBar(props: RowProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row
|
<Row
|
||||||
{...props}
|
|
||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
paddingY="3"
|
paddingY="3"
|
||||||
paddingX="3"
|
paddingX="3"
|
||||||
paddingRight="5"
|
paddingRight="5"
|
||||||
backgroundColor="2"
|
|
||||||
border="bottom"
|
border="bottom"
|
||||||
|
width="100%"
|
||||||
>
|
>
|
||||||
<Row alignItems="center">
|
<Row alignItems="center">
|
||||||
<Button onPress={() => setCollapsed(!isCollapsed)} variant="quiet">
|
<Button onPress={() => setCollapsed(!isCollapsed)} variant="quiet">
|
||||||
|
|
|
||||||
|
|
@ -10,25 +10,35 @@ export function SideNav(props: any) {
|
||||||
const [isCollapsed] = useGlobalState('sidenav-collapsed');
|
const [isCollapsed] = useGlobalState('sidenav-collapsed');
|
||||||
|
|
||||||
const links = [
|
const links = [
|
||||||
{
|
|
||||||
label: formatMessage(labels.boards),
|
|
||||||
href: renderTeamUrl('/boards'),
|
|
||||||
icon: <Lucide.LayoutDashboard />,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: formatMessage(labels.dashboard),
|
label: formatMessage(labels.dashboard),
|
||||||
href: renderTeamUrl('/dashboard'),
|
href: renderTeamUrl('/dashboard'),
|
||||||
icon: <Lucide.Copy />,
|
icon: <Lucide.Copy />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: formatMessage(labels.reports),
|
||||||
|
href: renderTeamUrl('/reports'),
|
||||||
|
icon: <Lucide.ChartArea />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: formatMessage(labels.websites),
|
label: formatMessage(labels.websites),
|
||||||
href: renderTeamUrl('/websites'),
|
href: renderTeamUrl('/websites'),
|
||||||
icon: <Lucide.Globe />,
|
icon: <Lucide.Globe />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: formatMessage(labels.reports),
|
label: formatMessage(labels.boards),
|
||||||
href: renderTeamUrl('/reports'),
|
href: renderTeamUrl('/boards'),
|
||||||
icon: <Lucide.ChartArea />,
|
icon: <Lucide.LayoutDashboard />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: formatMessage(labels.links),
|
||||||
|
href: renderTeamUrl('/links'),
|
||||||
|
icon: <Lucide.Link />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: formatMessage(labels.pixels),
|
||||||
|
href: renderTeamUrl('/pixels'),
|
||||||
|
icon: <Lucide.Grid2X2 />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: formatMessage(labels.settings),
|
label: formatMessage(labels.settings),
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { Button } from '@umami/react-zen';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import Script from 'next/script';
|
import Script from 'next/script';
|
||||||
import { WebsiteSelect } from '@/components/input/WebsiteSelect';
|
import { WebsiteSelect } from '@/components/input/WebsiteSelect';
|
||||||
import { Page } from '@/components/common/Page';
|
import { PageBody } from '@/components/common/PageBody';
|
||||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||||
import { EventsChart } from '@/components/metrics/EventsChart';
|
import { EventsChart } from '@/components/metrics/EventsChart';
|
||||||
import { WebsiteChart } from '../websites/[websiteId]/WebsiteChart';
|
import { WebsiteChart } from '../websites/[websiteId]/WebsiteChart';
|
||||||
|
|
@ -117,7 +117,7 @@ export function TestConsole({ websiteId }: { websiteId: string }) {
|
||||||
const website = data?.data.find(({ id }) => websiteId === id);
|
const website = data?.data.find(({ id }) => websiteId === id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page isLoading={isLoading} error={error}>
|
<PageBody isLoading={isLoading} error={error}>
|
||||||
<SectionHeader title="Test console">
|
<SectionHeader title="Test console">
|
||||||
<WebsiteSelect websiteId={website?.id} onSelect={handleChange} />
|
<WebsiteSelect websiteId={website?.id} onSelect={handleChange} />
|
||||||
</SectionHeader>
|
</SectionHeader>
|
||||||
|
|
@ -214,6 +214,6 @@ export function TestConsole({ websiteId }: { websiteId: string }) {
|
||||||
<EventsChart websiteId={website.id} />
|
<EventsChart websiteId={website.id} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Page>
|
</PageBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,16 @@ import { Metadata } from 'next';
|
||||||
import { ReportsHeader } from './ReportsHeader';
|
import { ReportsHeader } from './ReportsHeader';
|
||||||
import { ReportsDataTable } from './ReportsDataTable';
|
import { ReportsDataTable } from './ReportsDataTable';
|
||||||
import { useNavigation } from '@/components/hooks';
|
import { useNavigation } from '@/components/hooks';
|
||||||
|
import { PageBody } from '@/components/common/PageBody';
|
||||||
|
|
||||||
export function ReportsPage() {
|
export function ReportsPage() {
|
||||||
const { teamId } = useNavigation();
|
const { teamId } = useNavigation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<PageBody>
|
||||||
<ReportsHeader />
|
<ReportsHeader />
|
||||||
<ReportsDataTable teamId={teamId} />
|
<ReportsDataTable teamId={teamId} />
|
||||||
</>
|
</PageBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { useLoginQuery, useMessages, useNavigation } from '@/components/hooks';
|
||||||
import { SideMenu } from '@/components/common/SideMenu';
|
import { SideMenu } from '@/components/common/SideMenu';
|
||||||
import { PageHeader } from '@/components/common/PageHeader';
|
import { PageHeader } from '@/components/common/PageHeader';
|
||||||
import { Panel } from '@/components/common/Panel';
|
import { Panel } from '@/components/common/Panel';
|
||||||
|
import { PageBody } from '@/components/common/PageBody';
|
||||||
|
|
||||||
export function SettingsLayout({ children }: { children: ReactNode }) {
|
export function SettingsLayout({ children }: { children: ReactNode }) {
|
||||||
const { user } = useLoginQuery();
|
const { user } = useLoginQuery();
|
||||||
|
|
@ -33,11 +34,11 @@ export function SettingsLayout({ children }: { children: ReactNode }) {
|
||||||
const value = items.find(({ url }) => pathname.includes(url))?.id;
|
const value = items.find(({ url }) => pathname.includes(url))?.id;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<PageBody>
|
||||||
<Column gap="6">
|
<Column gap="6">
|
||||||
<PageHeader title={formatMessage(labels.settings)} />
|
<PageHeader title={formatMessage(labels.settings)} />
|
||||||
|
<Grid columns="160px 1fr" gap>
|
||||||
<Grid columns="160px 1fr" gap="6">
|
<Column>
|
||||||
<Column marginTop="6">
|
|
||||||
<SideMenu items={items} selectedKey={value} />
|
<SideMenu items={items} selectedKey={value} />
|
||||||
</Column>
|
</Column>
|
||||||
<Column>
|
<Column>
|
||||||
|
|
@ -45,5 +46,6 @@ export function SettingsLayout({ children }: { children: ReactNode }) {
|
||||||
</Column>
|
</Column>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Column>
|
</Column>
|
||||||
|
</PageBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ export function WebsitesTable({
|
||||||
return (
|
return (
|
||||||
<DataTable data={data}>
|
<DataTable data={data}>
|
||||||
<DataColumn id="name" label={formatMessage(labels.name)}>
|
<DataColumn id="name" label={formatMessage(labels.name)}>
|
||||||
{(row: any) => <Link href={`/websites/${row.id}`}>{row.name}</Link>}
|
{(row: any) => <Link href={renderTeamUrl(`/websites/${row.id}`)}>{row.name}</Link>}
|
||||||
</DataColumn>
|
</DataColumn>
|
||||||
<DataColumn id="domain" label={formatMessage(labels.domain)} />
|
<DataColumn id="domain" label={formatMessage(labels.domain)} />
|
||||||
{showActions && (
|
{showActions && (
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { Grid, Column } from '@umami/react-zen';
|
||||||
import { SideMenu } from '@/components/common/SideMenu';
|
import { SideMenu } from '@/components/common/SideMenu';
|
||||||
import { Panel } from '@/components/common/Panel';
|
import { Panel } from '@/components/common/Panel';
|
||||||
import { PageHeader } from '@/components/common/PageHeader';
|
import { PageHeader } from '@/components/common/PageHeader';
|
||||||
|
import { PageBody } from '@/components/common/PageBody';
|
||||||
|
|
||||||
export function TeamSettingsLayout({ children }: { children: ReactNode }) {
|
export function TeamSettingsLayout({ children }: { children: ReactNode }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
@ -31,11 +32,11 @@ export function TeamSettingsLayout({ children }: { children: ReactNode }) {
|
||||||
const value = items.find(({ url }) => pathname.includes(url))?.id;
|
const value = items.find(({ url }) => pathname.includes(url))?.id;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<PageBody>
|
||||||
<Column gap="6">
|
<Column gap="6">
|
||||||
<PageHeader title={formatMessage(labels.teamSettings)} />
|
<PageHeader title={formatMessage(labels.teamSettings)} />
|
||||||
<Column gap="6">
|
<Grid columns="200px 1fr" gap>
|
||||||
<Grid columns="200px 1fr">
|
<Column>
|
||||||
<Column marginTop="6">
|
|
||||||
<SideMenu items={items} selectedKey={value} />
|
<SideMenu items={items} selectedKey={value} />
|
||||||
</Column>
|
</Column>
|
||||||
<Column>
|
<Column>
|
||||||
|
|
@ -43,6 +44,6 @@ export function TeamSettingsLayout({ children }: { children: ReactNode }) {
|
||||||
</Column>
|
</Column>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Column>
|
</Column>
|
||||||
</Column>
|
</PageBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,14 @@ import { Column } from '@umami/react-zen';
|
||||||
import { PageHeader } from '@/components/common/PageHeader';
|
import { PageHeader } from '@/components/common/PageHeader';
|
||||||
import { WebsiteAddButton } from '@/app/(main)/settings/websites/WebsiteAddButton';
|
import { WebsiteAddButton } from '@/app/(main)/settings/websites/WebsiteAddButton';
|
||||||
import { Panel } from '@/components/common/Panel';
|
import { Panel } from '@/components/common/Panel';
|
||||||
|
import { PageBody } from '@/components/common/PageBody';
|
||||||
|
|
||||||
export function WebsitesPage() {
|
export function WebsitesPage() {
|
||||||
const { teamId } = useNavigation();
|
const { teamId } = useNavigation();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<PageBody>
|
||||||
<Column gap="6">
|
<Column gap="6">
|
||||||
<PageHeader title={formatMessage(labels.websites)}>
|
<PageHeader title={formatMessage(labels.websites)}>
|
||||||
<WebsiteAddButton teamId={teamId} />
|
<WebsiteAddButton teamId={teamId} />
|
||||||
|
|
@ -19,5 +21,6 @@ export function WebsitesPage() {
|
||||||
<WebsitesDataTable teamId={teamId} allowEdit={false} />
|
<WebsitesDataTable teamId={teamId} allowEdit={false} />
|
||||||
</Panel>
|
</Panel>
|
||||||
</Column>
|
</Column>
|
||||||
|
</PageBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { useMemo } from 'react';
|
||||||
import { firstBy } from 'thenby';
|
import { firstBy } from 'thenby';
|
||||||
import { WebsiteChart } from './WebsiteChart';
|
import { WebsiteChart } from './WebsiteChart';
|
||||||
import { useDashboard } from '@/store/dashboard';
|
import { useDashboard } from '@/store/dashboard';
|
||||||
import { WebsiteHeader } from './WebsiteHeader';
|
import { WebsiteControls } from './WebsiteControls';
|
||||||
import { WebsiteMetricsBar } from './WebsiteMetricsBar';
|
import { WebsiteMetricsBar } from './WebsiteMetricsBar';
|
||||||
import { useMessages, useNavigation } from '@/components/hooks';
|
import { useMessages, useNavigation } from '@/components/hooks';
|
||||||
import { LinkButton } from '@/components/common/LinkButton';
|
import { LinkButton } from '@/components/common/LinkButton';
|
||||||
|
|
@ -33,7 +33,7 @@ export function WebsiteChartList({
|
||||||
{ordered.map(({ id }, index) => {
|
{ordered.map(({ id }, index) => {
|
||||||
return index < limit ? (
|
return index < limit ? (
|
||||||
<div key={id}>
|
<div key={id}>
|
||||||
<WebsiteHeader websiteId={id} showLinks={false}>
|
<WebsiteControls websiteId={id} showLinks={false}>
|
||||||
<LinkButton href={renderTeamUrl(`/websites/${id}`)} variant="primary">
|
<LinkButton href={renderTeamUrl(`/websites/${id}`)} variant="primary">
|
||||||
<Text>{formatMessage(labels.viewDetails)}</Text>
|
<Text>{formatMessage(labels.viewDetails)}</Text>
|
||||||
<Icon>
|
<Icon>
|
||||||
|
|
@ -42,7 +42,7 @@ export function WebsiteChartList({
|
||||||
</Icon>
|
</Icon>
|
||||||
</Icon>
|
</Icon>
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
</WebsiteHeader>
|
</WebsiteControls>
|
||||||
<WebsiteMetricsBar websiteId={id} showChange={true} />
|
<WebsiteMetricsBar websiteId={id} showChange={true} />
|
||||||
{showCharts && <WebsiteChart websiteId={id} />}
|
{showCharts && <WebsiteChart websiteId={id} />}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
24
src/app/(main)/websites/[websiteId]/WebsiteControls.tsx
Normal file
24
src/app/(main)/websites/[websiteId]/WebsiteControls.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { Column, Row } from '@umami/react-zen';
|
||||||
|
import { WebsiteFilterButton } from '@/app/(main)/websites/[websiteId]/WebsiteFilterButton';
|
||||||
|
import { WebsiteDateFilter } from '@/components/input/WebsiteDateFilter';
|
||||||
|
import { FilterBar } from '@/components/input/FilterBar';
|
||||||
|
|
||||||
|
export function WebsiteControls({
|
||||||
|
websiteId,
|
||||||
|
showFilter = true,
|
||||||
|
}: {
|
||||||
|
websiteId: string;
|
||||||
|
showFilter?: boolean;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Column marginBottom="6" gap="3">
|
||||||
|
<Row alignItems="center" justifyContent="space-between" gap="3" paddingY="3">
|
||||||
|
{showFilter && <WebsiteFilterButton websiteId={websiteId} />}
|
||||||
|
<Row alignItems="center" gap="3">
|
||||||
|
<WebsiteDateFilter websiteId={websiteId} />
|
||||||
|
</Row>
|
||||||
|
</Row>
|
||||||
|
<FilterBar />
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { Button, Icon, Icons, DialogTrigger, Dialog, Modal, Text } from '@umami/react-zen';
|
import { Button, Icon, DialogTrigger, Dialog, Modal, Text } from '@umami/react-zen';
|
||||||
|
import { Lucide } from '@/components/icons';
|
||||||
import { FilterEditForm } from '@/components/common/FilterEditForm';
|
import { FilterEditForm } from '@/components/common/FilterEditForm';
|
||||||
import { useMessages, useNavigation, useFilters } from '@/components/hooks';
|
import { useMessages, useNavigation, useFilters } from '@/components/hooks';
|
||||||
|
|
||||||
|
|
@ -33,9 +34,9 @@ export function WebsiteFilterButton({
|
||||||
<DialogTrigger>
|
<DialogTrigger>
|
||||||
<Button variant="quiet">
|
<Button variant="quiet">
|
||||||
<Icon>
|
<Icon>
|
||||||
<Icons.Plus />
|
<Lucide.ListFilter />
|
||||||
</Icon>
|
</Icon>
|
||||||
{showText && <Text>{formatMessage(labels.filter)}</Text>}
|
{showText && <Text weight="bold">{formatMessage(labels.filter)}</Text>}
|
||||||
</Button>
|
</Button>
|
||||||
<Modal>
|
<Modal>
|
||||||
<Dialog>
|
<Dialog>
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,28 @@
|
||||||
import { Column, Row, Heading } from '@umami/react-zen';
|
import { Button, Icon, Text, Row } from '@umami/react-zen';
|
||||||
import { Favicon } from '@/components/common/Favicon';
|
import { PageHeader } from '@/components/common/PageHeader';
|
||||||
import { ActiveUsers } from '@/components/metrics/ActiveUsers';
|
|
||||||
import { useWebsite } from '@/components/hooks/useWebsite';
|
import { useWebsite } from '@/components/hooks/useWebsite';
|
||||||
import { WebsiteFilterButton } from '@/app/(main)/websites/[websiteId]/WebsiteFilterButton';
|
import { Lucide } from '@/components/icons';
|
||||||
import { WebsiteDateFilter } from '@/components/input/WebsiteDateFilter';
|
import { Favicon } from '@/components/common/Favicon';
|
||||||
import { FilterBar } from '@/components/metrics/FilterBar';
|
|
||||||
import { WebsiteMenu } from '@/app/(main)/websites/[websiteId]/WebsiteMenu';
|
|
||||||
|
|
||||||
export function WebsiteHeader({
|
export function WebsiteHeader() {
|
||||||
websiteId,
|
|
||||||
showFilter = true,
|
|
||||||
allowEdit = true,
|
|
||||||
}: {
|
|
||||||
websiteId: string;
|
|
||||||
showFilter?: boolean;
|
|
||||||
allowEdit?: boolean;
|
|
||||||
}) {
|
|
||||||
const website = useWebsite();
|
const website = useWebsite();
|
||||||
const { name, domain } = website || {};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column marginY="6" gap="6">
|
<PageHeader title={website.name} icon={<Favicon domain={website.domain} />}>
|
||||||
<Row alignItems="center" justifyContent="space-between" gap="3">
|
<Row alignItems="center" gap>
|
||||||
<Row alignItems="center" gap="3">
|
<Button>
|
||||||
<Favicon domain={domain} />
|
<Icon>
|
||||||
<Heading>{name}</Heading>
|
<Lucide.Share />
|
||||||
|
</Icon>
|
||||||
|
<Text>Share</Text>
|
||||||
|
</Button>
|
||||||
|
<Button>
|
||||||
|
<Icon>
|
||||||
|
<Lucide.Edit />
|
||||||
|
</Icon>
|
||||||
|
<Text>Edit</Text>
|
||||||
|
</Button>
|
||||||
</Row>
|
</Row>
|
||||||
<ActiveUsers websiteId={websiteId} />
|
</PageHeader>
|
||||||
<Row alignItems="center" gap="3">
|
|
||||||
{showFilter && <WebsiteFilterButton websiteId={websiteId} />}
|
|
||||||
<WebsiteDateFilter websiteId={websiteId} />
|
|
||||||
{allowEdit && <WebsiteMenu websiteId={websiteId} />}
|
|
||||||
</Row>
|
|
||||||
</Row>
|
|
||||||
<FilterBar websiteId={websiteId} />
|
|
||||||
</Column>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,27 @@
|
||||||
'use client';
|
'use client';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { Grid, Column, Box } from '@umami/react-zen';
|
import { Grid, Column } from '@umami/react-zen';
|
||||||
import { WebsiteProvider } from './WebsiteProvider';
|
import { WebsiteProvider } from './WebsiteProvider';
|
||||||
|
import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls';
|
||||||
|
import { WebsiteNav } from '@/app/(main)/websites/[websiteId]/WebsiteNav';
|
||||||
|
import { PageBody } from '@/components/common/PageBody';
|
||||||
import { WebsiteHeader } from '@/app/(main)/websites/[websiteId]/WebsiteHeader';
|
import { WebsiteHeader } from '@/app/(main)/websites/[websiteId]/WebsiteHeader';
|
||||||
import { WebsiteTabs } from '@/app/(main)/websites/[websiteId]/WebsiteTabs';
|
|
||||||
|
|
||||||
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}>
|
||||||
<WebsiteHeader websiteId={websiteId} />
|
<PageBody>
|
||||||
<Grid columns="170px 1140px" justifyContent="center" gap>
|
<WebsiteHeader />
|
||||||
<Box position="sticky" top="20px" alignSelf="flex-start">
|
<Grid columns="auto 1fr" justifyContent="center" gap width="100%">
|
||||||
<WebsiteTabs websiteId={websiteId} />
|
<Column position="sticky" top="0px" alignSelf="flex-start" width="200px" paddingTop="3">
|
||||||
</Box>
|
<WebsiteNav websiteId={websiteId} />
|
||||||
<Column>{children}</Column>
|
</Column>
|
||||||
|
<Column>
|
||||||
|
<WebsiteControls websiteId={websiteId} />
|
||||||
|
{children}
|
||||||
|
</Column>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
</PageBody>
|
||||||
</WebsiteProvider>
|
</WebsiteProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { Icons } from '@/components/icons';
|
||||||
import { useMessages, useNavigation } from '@/components/hooks';
|
import { useMessages, useNavigation } from '@/components/hooks';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
export function WebsiteTabs({ websiteId }: { websiteId: string }) {
|
export function WebsiteNav({ websiteId }: { websiteId: string }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { pathname, renderTeamUrl } = useNavigation();
|
const { pathname, renderTeamUrl } = useNavigation();
|
||||||
|
|
||||||
|
|
@ -60,7 +60,7 @@ export function WebsiteTabs({ websiteId }: { websiteId: string }) {
|
||||||
id: 'retention',
|
id: 'retention',
|
||||||
label: formatMessage(labels.retention),
|
label: formatMessage(labels.retention),
|
||||||
icon: <Icons.Magnet />,
|
icon: <Icons.Magnet />,
|
||||||
path: '/funnels',
|
path: '/retention',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'utm',
|
id: 'utm',
|
||||||
|
|
@ -97,7 +97,7 @@ export function WebsiteTabs({ websiteId }: { websiteId: string }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link key={id} href={renderTeamUrl(`/websites/${websiteId}${path}`)}>
|
<Link key={id} href={renderTeamUrl(`/websites/${websiteId}${path}`)}>
|
||||||
<NavMenuItem highlightColor="5" isSelected={isSelected}>
|
<NavMenuItem isSelected={isSelected}>
|
||||||
<Row alignItems="center" gap>
|
<Row alignItems="center" gap>
|
||||||
<Icon style={{ fill: 'currentcolor' }}>{icon}</Icon>
|
<Icon style={{ fill: 'currentcolor' }}>{icon}</Icon>
|
||||||
<Text>{label}</Text>
|
<Text>{label}</Text>
|
||||||
|
|
@ -8,5 +8,5 @@ export default async function ({ params }: { params: Promise<{ websiteId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Event Data',
|
title: 'Events',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
6
src/app/(main)/websites/[websiteId]/goals/GoalsPage.tsx
Normal file
6
src/app/(main)/websites/[websiteId]/goals/GoalsPage.tsx
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
'use client';
|
||||||
|
import { Column } from '@umami/react-zen';
|
||||||
|
|
||||||
|
export function GoalsPage({ websiteId }: { websiteId: string }) {
|
||||||
|
return <Column>Goals {websiteId}</Column>;
|
||||||
|
}
|
||||||
12
src/app/(main)/websites/[websiteId]/goals/page.tsx
Normal file
12
src/app/(main)/websites/[websiteId]/goals/page.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Metadata } from 'next';
|
||||||
|
import { GoalsPage } from './GoalsPage';
|
||||||
|
|
||||||
|
export default async function ({ params }: { params: Promise<{ websiteId: string }> }) {
|
||||||
|
const { websiteId } = await params;
|
||||||
|
|
||||||
|
return <GoalsPage websiteId={websiteId} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Goals',
|
||||||
|
};
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { Page } from '@/components/common/Page';
|
import { PageBody } from '@/components/common/PageBody';
|
||||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||||
import { useApi, useMessages } from '@/components/hooks';
|
import { useApi, useMessages } from '@/components/hooks';
|
||||||
import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder';
|
import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder';
|
||||||
|
|
@ -21,11 +21,11 @@ export function RealtimeHome() {
|
||||||
}, [data, router]);
|
}, [data, router]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page isLoading={isLoading || data?.length > 0} error={error}>
|
<PageBody isLoading={isLoading || data?.length > 0} error={error}>
|
||||||
<SectionHeader title={formatMessage(labels.realtime)} />
|
<SectionHeader title={formatMessage(labels.realtime)} />
|
||||||
{data?.length === 0 && (
|
{data?.length === 0 && (
|
||||||
<EmptyPlaceholder message={formatMessage(messages.noWebsitesConfigured)} />
|
<EmptyPlaceholder message={formatMessage(messages.noWebsitesConfigured)} />
|
||||||
)}
|
)}
|
||||||
</Page>
|
</PageBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import { firstBy } from 'thenby';
|
import { firstBy } from 'thenby';
|
||||||
import { Grid } from '@umami/react-zen';
|
import { Grid } from '@umami/react-zen';
|
||||||
import { GridRow } from '@/components/common/GridRow';
|
import { GridRow } from '@/components/common/GridRow';
|
||||||
import { Page } from '@/components/common/Page';
|
import { PageBody } from '@/components/common/PageBody';
|
||||||
import { Panel } from '@/components/common/Panel';
|
import { Panel } from '@/components/common/Panel';
|
||||||
import { RealtimeChart } from '@/components/metrics/RealtimeChart';
|
import { RealtimeChart } from '@/components/metrics/RealtimeChart';
|
||||||
import { WorldMap } from '@/components/metrics/WorldMap';
|
import { WorldMap } from '@/components/metrics/WorldMap';
|
||||||
|
|
@ -17,7 +17,7 @@ export function WebsiteRealtimePage({ websiteId }: { websiteId: string }) {
|
||||||
const { data, isLoading, error } = useRealtimeQuery(websiteId);
|
const { data, isLoading, error } = useRealtimeQuery(websiteId);
|
||||||
|
|
||||||
if (isLoading || error) {
|
if (isLoading || error) {
|
||||||
return <Page isLoading={isLoading} error={error} />;
|
return <PageBody isLoading={isLoading} error={error} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const countries = percentFilter(
|
const countries = percentFilter(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
'use client';
|
||||||
|
import { RetentionTable } from './RetentionTable';
|
||||||
|
|
||||||
|
export function RetentionPage({ websiteId }: { websiteId: string }) {
|
||||||
|
return <RetentionTable websiteId={websiteId} />;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder';
|
||||||
|
import { useMessages, useLocale, useReport } from '@/components/hooks';
|
||||||
|
import { formatDate } from '@/lib/date';
|
||||||
|
|
||||||
|
const DAYS = [1, 2, 3, 4, 5, 6, 7, 14, 21, 28];
|
||||||
|
|
||||||
|
export function RetentionTable({ days = DAYS }) {
|
||||||
|
const { formatMessage, labels } = useMessages();
|
||||||
|
const { locale } = useLocale();
|
||||||
|
const { report } = useReport();
|
||||||
|
const { data } = report || {};
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return <EmptyPlaceholder />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = data.reduce((arr: any[], row: { date: any; visitors: any; day: any }) => {
|
||||||
|
const { date, visitors, day } = row;
|
||||||
|
if (day === 0) {
|
||||||
|
return arr.concat({
|
||||||
|
date,
|
||||||
|
visitors,
|
||||||
|
records: days
|
||||||
|
.reduce((arr, day) => {
|
||||||
|
arr[day] = data.find(x => x.date === date && x.day === day);
|
||||||
|
return arr;
|
||||||
|
}, [])
|
||||||
|
.filter(n => n),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const totalDays = rows.length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div>{formatMessage(labels.date)}</div>
|
||||||
|
<div>{formatMessage(labels.visitors)}</div>
|
||||||
|
{days.map(n => (
|
||||||
|
<div key={n}>
|
||||||
|
{formatMessage(labels.day)} {n}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{rows.map(({ date, visitors, records }, rowIndex) => {
|
||||||
|
return (
|
||||||
|
<div key={rowIndex}>
|
||||||
|
<div>{formatDate(date, 'PP', locale)}</div>
|
||||||
|
<div>{visitors}</div>
|
||||||
|
{days.map(day => {
|
||||||
|
if (totalDays - rowIndex < day) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const percentage = records.filter(a => a.day === day)[0]?.percentage;
|
||||||
|
return <div key={day}>{percentage ? `${Number(percentage).toFixed(2)}%` : ''}</div>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
12
src/app/(main)/websites/[websiteId]/retention/page.tsx
Normal file
12
src/app/(main)/websites/[websiteId]/retention/page.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Metadata } from 'next';
|
||||||
|
import { RetentionPage } from './RetentionPage';
|
||||||
|
|
||||||
|
export default async function ({ params }: { params: Promise<{ websiteId: string }> }) {
|
||||||
|
const { websiteId } = await params;
|
||||||
|
|
||||||
|
return <RetentionPage websiteId={websiteId} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Retention',
|
||||||
|
};
|
||||||
|
|
@ -15,7 +15,7 @@ export function SessionsTable({ data = [] }: { data: any[]; showDomain?: boolean
|
||||||
<DataColumn id="id" label={formatMessage(labels.session)} width="100px">
|
<DataColumn id="id" label={formatMessage(labels.session)} width="100px">
|
||||||
{(row: any) => (
|
{(row: any) => (
|
||||||
<Link href={`sessions/${row.id}`} className={styles.link}>
|
<Link href={`sessions/${row.id}`} className={styles.link}>
|
||||||
<Avatar key={row.id} seed={row.id} size={64} />
|
<Avatar seed={row.id} size={64} />
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</DataColumn>
|
</DataColumn>
|
||||||
|
|
|
||||||
6
src/app/(main)/websites/[websiteId]/utm/UTMPage.tsx
Normal file
6
src/app/(main)/websites/[websiteId]/utm/UTMPage.tsx
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
'use client';
|
||||||
|
import { Column } from '@umami/react-zen';
|
||||||
|
|
||||||
|
export function UTMPage({ websiteId }: { websiteId: string }) {
|
||||||
|
return <Column>Goals {websiteId}</Column>;
|
||||||
|
}
|
||||||
12
src/app/(main)/websites/[websiteId]/utm/page.tsx
Normal file
12
src/app/(main)/websites/[websiteId]/utm/page.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Metadata } from 'next';
|
||||||
|
import { UTMPage } from './UTMPage';
|
||||||
|
|
||||||
|
export default async function ({ params }: { params: Promise<{ websiteId: string }> }) {
|
||||||
|
const { websiteId } = await params;
|
||||||
|
|
||||||
|
return <UTMPage websiteId={websiteId} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'UTM Parameters',
|
||||||
|
};
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import { WebsiteProvider } from '@/app/(main)/websites/[websiteId]/WebsiteProvider';
|
import { WebsiteProvider } from '@/app/(main)/websites/[websiteId]/WebsiteProvider';
|
||||||
import { WebsiteDetailsPage } from '@/app/(main)/websites/[websiteId]/WebsiteDetailsPage';
|
import { WebsiteDetailsPage } from '@/app/(main)/websites/[websiteId]/WebsiteDetailsPage';
|
||||||
import { useShareTokenQuery } from '@/components/hooks';
|
import { useShareTokenQuery } from '@/components/hooks';
|
||||||
import { Page } from '@/components/common/Page';
|
import { PageBody } from '@/components/common/PageBody';
|
||||||
import { Header } from './Header';
|
import { Header } from './Header';
|
||||||
import { Footer } from './Footer';
|
import { Footer } from './Footer';
|
||||||
|
|
||||||
|
|
@ -14,12 +14,12 @@ export function SharePage({ shareId }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<PageBody>
|
||||||
<Header />
|
<Header />
|
||||||
<WebsiteProvider websiteId={shareToken.websiteId}>
|
<WebsiteProvider websiteId={shareToken.websiteId}>
|
||||||
<WebsiteDetailsPage websiteId={shareToken.websiteId} />
|
<WebsiteDetailsPage websiteId={shareToken.websiteId} />
|
||||||
</WebsiteProvider>
|
</WebsiteProvider>
|
||||||
<Footer />
|
<Footer />
|
||||||
</Page>
|
</PageBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@ const LAYOUTS = {
|
||||||
two: {
|
two: {
|
||||||
columns: {
|
columns: {
|
||||||
xs: '1fr',
|
xs: '1fr',
|
||||||
md: 'repeat(auto-fill, minmax(600px, 1fr))',
|
md: 'repeat(auto-fill, minmax(560px, 1fr))',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
three: {
|
three: {
|
||||||
columns: {
|
columns: {
|
||||||
xs: '1fr',
|
xs: '1fr',
|
||||||
md: 'repeat(auto-fill, minmax(400px, 1fr))',
|
md: 'repeat(auto-fill, minmax(360px, 1fr))',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'one-two': { columns: { xs: '1fr', lg: 'repeat(3, 1fr)' } },
|
'one-two': { columns: { xs: '1fr', lg: 'repeat(3, 1fr)' } },
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,14 @@ import { ReactNode } from 'react';
|
||||||
import { AlertBanner, Loading, Column } from '@umami/react-zen';
|
import { AlertBanner, Loading, Column } from '@umami/react-zen';
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages } from '@/components/hooks';
|
||||||
|
|
||||||
export function Page({
|
export function PageBody({
|
||||||
|
maxWidth = '1600px',
|
||||||
error,
|
error,
|
||||||
isLoading,
|
isLoading,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
className?: string;
|
maxWidth?: string;
|
||||||
error?: unknown;
|
error?: unknown;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
|
|
@ -25,7 +26,7 @@ export function Page({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column {...props} width="100%" maxWidth="1320px" margin="auto" paddingBottom="9">
|
<Column {...props} width="100%" paddingBottom="9" style={{ margin: '0 auto', maxWidth }}>
|
||||||
{children}
|
{children}
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
|
|
@ -15,8 +15,14 @@ export function PageHeader({
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Row justifyContent="space-between" alignItems="center" paddingY="6" border="bottom">
|
<Row
|
||||||
<Row gap="3">
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
paddingY="6"
|
||||||
|
border="bottom"
|
||||||
|
width="100%"
|
||||||
|
>
|
||||||
|
<Row alignItems="center" gap="3">
|
||||||
{icon && <Icon>{icon}</Icon>}
|
{icon && <Icon>{icon}</Icon>}
|
||||||
{title && <Heading size="4">{title}</Heading>}
|
{title && <Heading size="4">{title}</Heading>}
|
||||||
{description && <Text color="muted">{description}</Text>}
|
{description && <Text color="muted">{description}</Text>}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { Text, List, ListItem, Icon, Row } from '@umami/react-zen';
|
import { Text, NavMenu, NavMenuItem, Icon, Row } from '@umami/react-zen';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
export interface SideMenuProps {
|
export interface SideMenuProps {
|
||||||
items: { id: string; label: string; url: string; icon?: ReactNode }[];
|
items: { id: string; label: string; url: string; icon?: ReactNode }[];
|
||||||
|
|
@ -8,17 +9,19 @@ export interface SideMenuProps {
|
||||||
|
|
||||||
export function SideMenu({ items, selectedKey }: SideMenuProps) {
|
export function SideMenu({ items, selectedKey }: SideMenuProps) {
|
||||||
return (
|
return (
|
||||||
<List>
|
<NavMenu highlightColor="3">
|
||||||
{items.map(({ id, label, url, icon }) => {
|
{items.map(({ id, label, url, icon }) => {
|
||||||
return (
|
return (
|
||||||
<ListItem key={id} id={id} href={url}>
|
<Link key={id} href={url}>
|
||||||
|
<NavMenuItem isSelected={id === selectedKey}>
|
||||||
<Row alignItems="center" gap>
|
<Row alignItems="center" gap>
|
||||||
{icon && <Icon>{icon}</Icon>}
|
{icon && <Icon>{icon}</Icon>}
|
||||||
<Text weight={id === selectedKey ? 'bold' : 'regular'}>{label}</Text>
|
<Text>{label}</Text>
|
||||||
</Row>
|
</Row>
|
||||||
</ListItem>
|
</NavMenuItem>
|
||||||
|
</Link>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</List>
|
</NavMenu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,8 @@ import { MouseEvent } from 'react';
|
||||||
import { Button, Icon, Icons, Text, Row, TooltipTrigger, Tooltip } from '@umami/react-zen';
|
import { Button, Icon, Icons, Text, Row, TooltipTrigger, Tooltip } from '@umami/react-zen';
|
||||||
import { useNavigation, useMessages, useFormat, useFilters } from '@/components/hooks';
|
import { useNavigation, useMessages, useFormat, useFilters } from '@/components/hooks';
|
||||||
import { isSearchOperator } from '@/lib/params';
|
import { isSearchOperator } from '@/lib/params';
|
||||||
import { WebsiteFilterButton } from '@/app/(main)/websites/[websiteId]/WebsiteFilterButton';
|
|
||||||
|
|
||||||
export function FilterBar({ websiteId }: { websiteId: string }) {
|
export function FilterBar() {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { formatValue } = useFormat();
|
const { formatValue } = useFormat();
|
||||||
const { router, renderUrl } = useNavigation();
|
const { router, renderUrl } = useNavigation();
|
||||||
|
|
@ -25,8 +24,9 @@ export function FilterBar({ websiteId }: { websiteId: string }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row
|
<Row
|
||||||
|
theme="dark"
|
||||||
|
backgroundColor="1"
|
||||||
gap
|
gap
|
||||||
backgroundColor="3"
|
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
paddingY="2"
|
paddingY="2"
|
||||||
|
|
@ -57,18 +57,21 @@ export function FilterBar({ websiteId }: { websiteId: string }) {
|
||||||
>
|
>
|
||||||
<Row alignItems="center" gap="4">
|
<Row alignItems="center" gap="4">
|
||||||
<Row alignItems="center" gap="2">
|
<Row alignItems="center" gap="2">
|
||||||
<Text weight="bold">{label}</Text>
|
<Text color="12" weight="bold">
|
||||||
|
{label}
|
||||||
|
</Text>
|
||||||
<Text color="11">{operatorLabels[operator]}</Text>
|
<Text color="11">{operatorLabels[operator]}</Text>
|
||||||
<Text weight="bold">{paramValue}</Text>
|
<Text color="12" weight="bold">
|
||||||
|
{paramValue}
|
||||||
|
</Text>
|
||||||
</Row>
|
</Row>
|
||||||
<Icon onClick={e => handleCloseFilter(name, e)} size="xs">
|
<Icon onClick={e => handleCloseFilter(name, e)} size="xs" color>
|
||||||
<Icons.Close />
|
<Icons.Close />
|
||||||
</Icon>
|
</Icon>
|
||||||
</Row>
|
</Row>
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<WebsiteFilterButton websiteId={websiteId} alignment="center" showText={false} />
|
|
||||||
</Row>
|
</Row>
|
||||||
<TooltipTrigger delay={0}>
|
<TooltipTrigger delay={0}>
|
||||||
<Button variant="quiet" onPress={handleResetFilter}>
|
<Button variant="quiet" onPress={handleResetFilter}>
|
||||||
|
|
@ -157,6 +157,7 @@ export const labels = defineMessages({
|
||||||
eventData: { id: 'label.event-data', defaultMessage: 'Event data' },
|
eventData: { id: 'label.event-data', defaultMessage: 'Event data' },
|
||||||
sessionData: { id: 'label.session-data', defaultMessage: 'Session data' },
|
sessionData: { id: 'label.session-data', defaultMessage: 'Session data' },
|
||||||
funnel: { id: 'label.funnel', defaultMessage: 'Funnel' },
|
funnel: { id: 'label.funnel', defaultMessage: 'Funnel' },
|
||||||
|
funnels: { id: 'label.funnels', defaultMessage: 'Funnels' },
|
||||||
funnelDescription: {
|
funnelDescription: {
|
||||||
id: 'label.funnel-description',
|
id: 'label.funnel-description',
|
||||||
defaultMessage: 'Understand the conversion and drop-off rate of users.',
|
defaultMessage: 'Understand the conversion and drop-off rate of users.',
|
||||||
|
|
@ -315,6 +316,8 @@ export const labels = defineMessages({
|
||||||
other: { id: 'label.other', defaultMessage: 'Other' },
|
other: { id: 'label.other', defaultMessage: 'Other' },
|
||||||
boards: { id: 'label.boards', defaultMessage: 'Boards' },
|
boards: { id: 'label.boards', defaultMessage: 'Boards' },
|
||||||
apply: { id: 'label.apply', defaultMessage: 'Apply' },
|
apply: { id: 'label.apply', defaultMessage: 'Apply' },
|
||||||
|
links: { id: 'label.links', defaultMessage: 'Links' },
|
||||||
|
pixels: { id: 'label.pixels', defaultMessage: 'Pixels' },
|
||||||
});
|
});
|
||||||
|
|
||||||
export const messages = defineMessages({
|
export const messages = defineMessages({
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue