mirror of
https://github.com/umami-software/umami.git
synced 2026-02-07 14:17:13 +01:00
Panels redesign.
This commit is contained in:
parent
7886c3f393
commit
f5bc3dc6c2
58 changed files with 530 additions and 733 deletions
|
|
@ -17,7 +17,6 @@ export function MenuBar(props: RowProps) {
|
|||
paddingY="3"
|
||||
paddingX="3"
|
||||
paddingRight="5"
|
||||
backgroundColor="1"
|
||||
>
|
||||
<Row>
|
||||
<Button onPress={() => setCollapsed(!isCollapsed)} variant="quiet">
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export function DateRangeSetting() {
|
|||
const handleReset = () => saveDateRange(DEFAULT_DATE_RANGE);
|
||||
|
||||
return (
|
||||
<Flexbox gap={10} width={300}>
|
||||
<Flexbox gap="3" width="300px">
|
||||
<DateFilter
|
||||
className={styles.field}
|
||||
value={value}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState } from 'react';
|
||||
import { Button, Select, ListItem, Flexbox } from '@umami/react-zen';
|
||||
import { Button, Select, ListItem, Row } from '@umami/react-zen';
|
||||
import { useLocale, useMessages } from '@/components/hooks';
|
||||
import { DEFAULT_LOCALE } from '@/lib/constants';
|
||||
import { languages } from '@/lib/lang';
|
||||
|
|
@ -26,7 +26,7 @@ export function LanguageSetting() {
|
|||
};
|
||||
|
||||
return (
|
||||
<Flexbox gap={10}>
|
||||
<Row gap="3">
|
||||
<Select
|
||||
value={locale}
|
||||
onChange={val => saveLocale(val as string)}
|
||||
|
|
@ -43,6 +43,6 @@ export function LanguageSetting() {
|
|||
{!items.length && <ListItem></ListItem>}
|
||||
</Select>
|
||||
<Button onPress={handleReset}>{formatMessage(labels.reset)}</Button>
|
||||
</Flexbox>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,5 +4,5 @@
|
|||
}
|
||||
|
||||
.active {
|
||||
border: 2px solid var(--primary400);
|
||||
border: 2px solid var(--primary-color);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ export function FieldFilterEditForm({
|
|||
<Column>
|
||||
<Row className={styles.filter}>
|
||||
<Label>{label}</Label>
|
||||
<Flexbox gap={10}>
|
||||
<Flexbox gap="3">
|
||||
{allowFilterSelect && (
|
||||
<Select
|
||||
className={styles.dropdown}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
position: relative;
|
||||
|
||||
--journey-line-color: var(--base600);
|
||||
--journey-active-color: var(--primary400);
|
||||
--journey-active-color: var(--primary-color);
|
||||
--journey-faded-color: var(--base300);
|
||||
}
|
||||
|
||||
|
|
@ -108,7 +108,7 @@
|
|||
|
||||
.node.active {
|
||||
color: var(--light50);
|
||||
background: var(--primary400);
|
||||
background: var(--primary-color);
|
||||
}
|
||||
|
||||
.node.selected .count {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { BarChart } from '@/components/charts/BarChart';
|
|||
import { PieChart } from '@/components/charts/PieChart';
|
||||
import { TypeIcon } from '@/components/common/TypeIcon';
|
||||
import { useCountryNames, useLocale, useMessages } from '@/components/hooks';
|
||||
import { GridRow } from '@/components/layout/Grid';
|
||||
import { GridRow } from '@/components/layout/GridRow';
|
||||
import { ListTable } from '@/components/metrics/ListTable';
|
||||
import { MetricCard } from '@/components/metrics/MetricCard';
|
||||
import { MetricsBar } from '@/components/metrics/MetricsBar';
|
||||
|
|
@ -133,7 +133,7 @@ export function RevenueView({ isLoading }: RevenueViewProps) {
|
|||
renderXLabel={renderDateLabels(dateRange?.unit, locale)}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<GridRow columns="two">
|
||||
<GridRow layout="two">
|
||||
<ListTable
|
||||
metric={formatMessage(labels.country)}
|
||||
data={data?.country.map(({ name, value }) => ({
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
'use client';
|
||||
import { ReactNode } from 'react';
|
||||
import { Grid, Column } from '@umami/react-zen';
|
||||
import { useLoginQuery, useMessages } from '@/components/hooks';
|
||||
import { MenuLayout } from '@/components/layout/MenuLayout';
|
||||
import { SideBar } from '@/components/layout/SideBar';
|
||||
|
||||
export function SettingsLayout({ children }: { children: ReactNode }) {
|
||||
const { user } = useLoginQuery();
|
||||
|
|
@ -21,5 +22,10 @@ export function SettingsLayout({ children }: { children: ReactNode }) {
|
|||
},
|
||||
].filter(n => n);
|
||||
|
||||
return <MenuLayout items={items}>{children}</MenuLayout>;
|
||||
return (
|
||||
<Grid>
|
||||
<SideBar items={items} />
|
||||
<Column>{children}</Column>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
'use client';
|
||||
import { ReactNode } from 'react';
|
||||
import { useMessages, useNavigation } from '@/components/hooks';
|
||||
import { MenuLayout } from '@/components/layout/MenuLayout';
|
||||
import { Grid, Column } from '@umami/react-zen';
|
||||
import { SideBar } from '@/components/layout/SideBar';
|
||||
|
||||
export function TeamSettingsLayout({ children }: { children: ReactNode }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
|
@ -25,5 +26,10 @@ export function TeamSettingsLayout({ children }: { children: ReactNode }) {
|
|||
},
|
||||
].filter(n => n);
|
||||
|
||||
return <MenuLayout items={items}>{children}</MenuLayout>;
|
||||
return (
|
||||
<Grid>
|
||||
<SideBar items={items} />
|
||||
<Column>{children}</Column>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,17 @@
|
|||
'use client';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { Column } from '@umami/react-zen';
|
||||
import { Panel } from '@/components/layout/Panel';
|
||||
import { FilterTags } from '@/components/metrics/FilterTags';
|
||||
import { useNavigation } from '@/components/hooks';
|
||||
import { FILTER_COLUMNS } from '@/lib/constants';
|
||||
import { WebsiteChart } from './WebsiteChart';
|
||||
import { WebsiteExpandedView } from './WebsiteExpandedView';
|
||||
import { WebsiteHeader } from './WebsiteHeader';
|
||||
import { WebsiteMetricsBar } from './WebsiteMetricsBar';
|
||||
import { WebsiteTableView } from './WebsiteTableView';
|
||||
import { FILTER_COLUMNS } from '@/lib/constants';
|
||||
|
||||
export function WebsiteDetailsPage({ websiteId }: { websiteId: string }) {
|
||||
const pathname = usePathname();
|
||||
const { query } = useNavigation();
|
||||
|
||||
const showLinks = !pathname.includes('/share/');
|
||||
const { view } = query;
|
||||
|
||||
const params = Object.keys(query).reduce((obj, key) => {
|
||||
|
|
@ -24,13 +22,15 @@ export function WebsiteDetailsPage({ websiteId }: { websiteId: string }) {
|
|||
}, {});
|
||||
|
||||
return (
|
||||
<>
|
||||
<WebsiteHeader websiteId={websiteId} showLinks={showLinks} />
|
||||
<Column gap="3">
|
||||
<WebsiteHeader websiteId={websiteId} />
|
||||
<FilterTags websiteId={websiteId} params={params} />
|
||||
<WebsiteMetricsBar websiteId={websiteId} showFilter={true} showChange={true} sticky={true} />
|
||||
<WebsiteChart websiteId={websiteId} />
|
||||
<WebsiteMetricsBar websiteId={websiteId} showFilter={true} showChange={true} />
|
||||
<Panel>
|
||||
<WebsiteChart websiteId={websiteId} />
|
||||
</Panel>
|
||||
{!view && <WebsiteTableView websiteId={websiteId} />}
|
||||
{view && <WebsiteExpandedView websiteId={websiteId} />}
|
||||
</>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Icon, Icons, Text, Grid, Column } from '@umami/react-zen';
|
||||
import { LinkButton } from '@/components/common/LinkButton';
|
||||
import { useMessages, useNavigation } from '@/components/hooks';
|
||||
import { MenuNav } from '@/components/layout/MenuNav';
|
||||
import { SideBar } from '@/components/layout/SideBar';
|
||||
import { BrowsersTable } from '@/components/metrics/BrowsersTable';
|
||||
import { CitiesTable } from '@/components/metrics/CitiesTable';
|
||||
import { CountriesTable } from '@/components/metrics/CountriesTable';
|
||||
|
|
@ -133,15 +133,8 @@ export function WebsiteExpandedView({
|
|||
|
||||
const DetailsComponent = views[view] || (() => null);
|
||||
|
||||
/*
|
||||
const handleChange = (view: any) => {
|
||||
router.push(renderUrl({ view }));
|
||||
};
|
||||
|
||||
const renderValue = (value: string) => items.find(({ key }) => key === value)?.label;
|
||||
*/
|
||||
return (
|
||||
<Grid columns="auto 1fr" gap="6">
|
||||
<Grid columns="auto 1fr" gap="6" marginTop="6">
|
||||
<Column gap="6" width="200px">
|
||||
<LinkButton href={renderUrl({ view: undefined })} variant="quiet" scroll={false}>
|
||||
<Icon rotate={180}>
|
||||
|
|
@ -149,7 +142,7 @@ export function WebsiteExpandedView({
|
|||
</Icon>
|
||||
<Text>{formatMessage(labels.back)}</Text>
|
||||
</LinkButton>
|
||||
<MenuNav items={items} selectedKey={view} />
|
||||
<SideBar items={items} selectedKey={view} />
|
||||
</Column>
|
||||
<Column>
|
||||
<DetailsComponent
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { Row, Heading } from '@umami/react-zen';
|
||||
import { Favicon } from '@/components/common/Favicon';
|
||||
import { useWebsiteQuery } from '@/components/hooks';
|
||||
import { ActiveUsers } from '@/components/metrics/ActiveUsers';
|
||||
import { WebsiteTabs } from '@/app/(main)/websites/[websiteId]/WebsiteTabs';
|
||||
import { useWebsite } from '@/components/hooks/useWebsite';
|
||||
|
||||
export function WebsiteHeader({
|
||||
websiteId,
|
||||
|
|
@ -12,7 +12,7 @@ export function WebsiteHeader({
|
|||
websiteId: string;
|
||||
children?: ReactNode;
|
||||
}) {
|
||||
const { data: website } = useWebsiteQuery(websiteId);
|
||||
const website = useWebsite();
|
||||
const { name, domain } = website || {};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Grid, GridRow } from '@/components/layout/Grid';
|
||||
import { Grid } from '@umami/react-zen';
|
||||
import { Panel } from '@/components/layout/Panel';
|
||||
import { PagesTable } from '@/components/metrics/PagesTable';
|
||||
import { ReferrersTable } from '@/components/metrics/ReferrersTable';
|
||||
import { BrowsersTable } from '@/components/metrics/BrowsersTable';
|
||||
|
|
@ -19,25 +20,43 @@ export function WebsiteTableView({ websiteId }: { websiteId: string }) {
|
|||
const isSharePage = pathname.includes('/share/');
|
||||
|
||||
return (
|
||||
<Grid>
|
||||
<GridRow columns="two">
|
||||
<PagesTable {...tableProps} />
|
||||
<ReferrersTable {...tableProps} />
|
||||
</GridRow>
|
||||
<GridRow columns="three">
|
||||
<BrowsersTable {...tableProps} />
|
||||
<OSTable {...tableProps} />
|
||||
<DevicesTable {...tableProps} />
|
||||
</GridRow>
|
||||
<GridRow columns="two-one">
|
||||
<WorldMap websiteId={websiteId} />
|
||||
<CountriesTable {...tableProps} />
|
||||
</GridRow>
|
||||
<Grid gap="3">
|
||||
<Grid gap="3" columns="repeat(auto-fill, minmax(500px, 1fr))">
|
||||
<Panel>
|
||||
<PagesTable {...tableProps} />
|
||||
</Panel>
|
||||
<Panel>
|
||||
<ReferrersTable {...tableProps} />
|
||||
</Panel>
|
||||
</Grid>
|
||||
<Grid gap="3" columns="repeat(auto-fill, minmax(400px, 1fr))">
|
||||
<Panel>
|
||||
<BrowsersTable {...tableProps} />
|
||||
</Panel>
|
||||
<Panel>
|
||||
<OSTable {...tableProps} />
|
||||
</Panel>
|
||||
<Panel>
|
||||
<DevicesTable {...tableProps} />
|
||||
</Panel>
|
||||
</Grid>
|
||||
<Grid gap="3" columns="2fr 1fr">
|
||||
<Panel padding="0">
|
||||
<WorldMap websiteId={websiteId} />
|
||||
</Panel>
|
||||
<Panel>
|
||||
<CountriesTable {...tableProps} />
|
||||
</Panel>
|
||||
</Grid>
|
||||
{isSharePage && (
|
||||
<GridRow columns="one-two">
|
||||
<EventsTable {...tableProps} />
|
||||
<EventsChart websiteId={websiteId} />
|
||||
</GridRow>
|
||||
<Grid gap="3">
|
||||
<Panel>
|
||||
<EventsTable {...tableProps} />
|
||||
</Panel>
|
||||
<Panel>
|
||||
<EventsChart websiteId={websiteId} />
|
||||
</Panel>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,47 +4,57 @@ import { useMessages, useNavigation } from '@/components/hooks';
|
|||
|
||||
export function WebsiteTabs({ websiteId }: { websiteId: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { renderTeamUrl } = useNavigation();
|
||||
const { pathname, renderTeamUrl } = useNavigation();
|
||||
|
||||
const links = [
|
||||
{
|
||||
id: 'overview',
|
||||
label: formatMessage(labels.overview),
|
||||
icon: <Icons.Overview />,
|
||||
path: '',
|
||||
},
|
||||
{
|
||||
id: 'events',
|
||||
label: formatMessage(labels.events),
|
||||
icon: <Icons.Lightning />,
|
||||
path: '/events',
|
||||
},
|
||||
{
|
||||
id: 'sessions',
|
||||
label: formatMessage(labels.sessions),
|
||||
icon: <Icons.User />,
|
||||
path: '/sessions',
|
||||
},
|
||||
{
|
||||
id: 'realtime',
|
||||
label: formatMessage(labels.realtime),
|
||||
icon: <Icons.Clock />,
|
||||
path: '/realtime',
|
||||
},
|
||||
{
|
||||
id: 'compare',
|
||||
label: formatMessage(labels.compare),
|
||||
icon: <Icons.Compare />,
|
||||
path: '/compare',
|
||||
},
|
||||
{
|
||||
id: 'reports',
|
||||
label: formatMessage(labels.reports),
|
||||
icon: <Icons.Reports />,
|
||||
path: '/reports',
|
||||
},
|
||||
].map((link, index) => ({ ...link, id: index }));
|
||||
];
|
||||
|
||||
const selectedKey = links
|
||||
? links.find(({ path }) => path && pathname.endsWith(path))?.id
|
||||
: 'overview';
|
||||
|
||||
return (
|
||||
<Tabs>
|
||||
<Tabs selectedKey={selectedKey}>
|
||||
<TabList items={links}>
|
||||
{({ label, icon, path }) => {
|
||||
{({ id, label, icon, path }) => {
|
||||
return (
|
||||
<Tab key={path} href={renderTeamUrl(`/websites/${websiteId}/${path}`)}>
|
||||
<Tab id={id} href={renderTeamUrl(`/websites/${websiteId}/${path}`)}>
|
||||
<Row gap="3" alignItems="center">
|
||||
<Icon fillColor="currentColor">{icon}</Icon>
|
||||
<Text>{label}</Text>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
'use client';
|
||||
import { Grid } from '@umami/react-zen';
|
||||
import { Panel } from '@/components/layout/Panel';
|
||||
import { WebsiteHeader } from '../WebsiteHeader';
|
||||
import { WebsiteMetricsBar } from '../WebsiteMetricsBar';
|
||||
import { FilterTags } from '@/components/metrics/FilterTags';
|
||||
|
|
@ -18,12 +20,16 @@ export function WebsiteComparePage({ websiteId }) {
|
|||
}, {});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid gap="3">
|
||||
<WebsiteHeader websiteId={websiteId} />
|
||||
<FilterTags websiteId={websiteId} params={params} />
|
||||
<WebsiteMetricsBar websiteId={websiteId} compareMode={true} showFilter={true} />
|
||||
<WebsiteChart websiteId={websiteId} compareMode={true} />
|
||||
<WebsiteCompareTables websiteId={websiteId} />
|
||||
</>
|
||||
<Panel>
|
||||
<WebsiteChart websiteId={websiteId} compareMode={true} />
|
||||
</Panel>
|
||||
<Panel>
|
||||
<WebsiteCompareTables websiteId={websiteId} />
|
||||
</Panel>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Grid, Heading, Column } from '@umami/react-zen';
|
||||
import { useDateRange, useMessages, useNavigation } from '@/components/hooks';
|
||||
import { Grid, GridRow } from '@/components/layout/Grid';
|
||||
import { MenuNav } from '@/components/layout/MenuNav';
|
||||
import { SideBar } from '@/components/layout/SideBar';
|
||||
import { BrowsersTable } from '@/components/metrics/BrowsersTable';
|
||||
import { ChangeLabel } from '@/components/metrics/ChangeLabel';
|
||||
import { CitiesTable } from '@/components/metrics/CitiesTable';
|
||||
|
|
@ -20,7 +20,6 @@ import { getCompareDate } from '@/lib/date';
|
|||
import { formatNumber } from '@/lib/format';
|
||||
import { useState } from 'react';
|
||||
import { useWebsites } from '@/store/websites';
|
||||
import styles from './WebsiteCompareTables.module.css';
|
||||
|
||||
const views = {
|
||||
url: PagesTable,
|
||||
|
|
@ -143,29 +142,22 @@ export function WebsiteCompareTables({ websiteId }: { websiteId: string }) {
|
|||
};
|
||||
|
||||
return (
|
||||
<Grid className={styles.container}>
|
||||
<GridRow columns="compare">
|
||||
<MenuNav className={styles.nav} items={items} selectedKey={view} shallow={true} />
|
||||
<div>
|
||||
<div className={styles.title}>{formatMessage(labels.previous)}</div>
|
||||
<Component
|
||||
websiteId={websiteId}
|
||||
limit={20}
|
||||
showMore={false}
|
||||
onDataLoad={setData}
|
||||
params={params}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className={styles.title}> {formatMessage(labels.current)}</div>
|
||||
<Component
|
||||
websiteId={websiteId}
|
||||
limit={20}
|
||||
showMore={false}
|
||||
renderChange={renderChange}
|
||||
/>
|
||||
</div>
|
||||
</GridRow>
|
||||
<Grid columns="300px 1fr 1fr" gap="3">
|
||||
<SideBar items={items} selectedKey={view} />
|
||||
<Column>
|
||||
<Heading>{formatMessage(labels.previous)}</Heading>
|
||||
<Component
|
||||
websiteId={websiteId}
|
||||
limit={20}
|
||||
showMore={false}
|
||||
onDataLoad={setData}
|
||||
params={params}
|
||||
/>
|
||||
</Column>
|
||||
<Column>
|
||||
<Heading> {formatMessage(labels.current)}</Heading>
|
||||
<Component websiteId={websiteId} limit={20} showMore={false} renderChange={renderChange} />
|
||||
</Column>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
.link:hover {
|
||||
cursor: pointer;
|
||||
color: var(--primary400);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.title {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
'use client';
|
||||
import { TabList, Tab, Tabs, TabPanel, Grid } from '@umami/react-zen';
|
||||
import { useState } from 'react';
|
||||
import { WebsiteHeader } from '../WebsiteHeader';
|
||||
import { EventsDataTable } from './EventsDataTable';
|
||||
import { EventsMetricsBar } from './EventsMetricsBar';
|
||||
import { Panel } from '@/components/layout/Panel';
|
||||
import { EventsChart } from '@/components/metrics/EventsChart';
|
||||
import { GridRow } from '@/components/layout/Grid';
|
||||
import { GridRow } from '@/components/layout/GridRow';
|
||||
import { MetricsTable } from '@/components/metrics/MetricsTable';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { TabList, Tab, Tabs } from '@umami/react-zen';
|
||||
import { useState } from 'react';
|
||||
import { EventProperties } from './EventProperties';
|
||||
|
||||
export function EventsPage({ websiteId }) {
|
||||
|
|
@ -15,32 +16,36 @@ export function EventsPage({ websiteId }) {
|
|||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid gap="3">
|
||||
<WebsiteHeader websiteId={websiteId} />
|
||||
<EventsMetricsBar websiteId={websiteId} />
|
||||
<GridRow columns="two-one">
|
||||
<EventsChart websiteId={websiteId} />
|
||||
<MetricsTable
|
||||
websiteId={websiteId}
|
||||
type="event"
|
||||
title={formatMessage(labels.events)}
|
||||
metric={formatMessage(labels.actions)}
|
||||
/>
|
||||
<GridRow layout="two-one">
|
||||
<Panel>
|
||||
<EventsChart websiteId={websiteId} />
|
||||
</Panel>
|
||||
<Panel>
|
||||
<MetricsTable
|
||||
websiteId={websiteId}
|
||||
type="event"
|
||||
title={formatMessage(labels.events)}
|
||||
metric={formatMessage(labels.actions)}
|
||||
/>
|
||||
</Panel>
|
||||
</GridRow>
|
||||
<div>
|
||||
<Tabs
|
||||
selectedKey={tab}
|
||||
onSelectionChange={(value: any) => setTab(value)}
|
||||
style={{ marginBottom: 30 }}
|
||||
>
|
||||
<Panel marginY="6">
|
||||
<Tabs selectedKey={tab} onSelectionChange={(value: any) => setTab(value)}>
|
||||
<TabList>
|
||||
<Tab key="activity">{formatMessage(labels.activity)}</Tab>
|
||||
<Tab key="properties">{formatMessage(labels.properties)}</Tab>
|
||||
<Tab id="activity">{formatMessage(labels.activity)}</Tab>
|
||||
<Tab id="properties">{formatMessage(labels.properties)}</Tab>
|
||||
</TabList>
|
||||
<TabPanel id="activity">
|
||||
<EventsDataTable websiteId={websiteId} />
|
||||
</TabPanel>
|
||||
<TabPanel id="properties">
|
||||
<EventProperties websiteId={websiteId} />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
{tab === 'activity' && <EventsDataTable websiteId={websiteId} />}
|
||||
{tab === 'properties' && <EventProperties websiteId={websiteId} />}
|
||||
</div>
|
||||
</>
|
||||
</Panel>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@
|
|||
}
|
||||
|
||||
.row .link:hover {
|
||||
color: var(--primary400);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.search {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
'use client';
|
||||
import { firstBy } from 'thenby';
|
||||
import { Grid, GridRow } from '@/components/layout/Grid';
|
||||
import { Grid } from '@umami/react-zen';
|
||||
import { GridRow } from '@/components/layout/GridRow';
|
||||
import { Page } from '@/components/layout/Page';
|
||||
import { Panel } from '@/components/layout/Panel';
|
||||
import { RealtimeChart } from '@/components/metrics/RealtimeChart';
|
||||
import { WorldMap } from '@/components/metrics/WorldMap';
|
||||
import { useRealtimeQuery } from '@/components/hooks';
|
||||
|
|
@ -26,20 +28,28 @@ export function WebsiteRealtimePage({ websiteId }) {
|
|||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid gap="3">
|
||||
<WebsiteHeader websiteId={websiteId} />
|
||||
<RealtimeHeader data={data} />
|
||||
<RealtimeChart data={data} unit="minute" />
|
||||
<Grid>
|
||||
<GridRow columns="one-two">
|
||||
<Panel>
|
||||
<RealtimeChart data={data} unit="minute" />
|
||||
</Panel>
|
||||
<GridRow layout="one-two">
|
||||
<Panel>
|
||||
<RealtimeUrls data={data} />
|
||||
</Panel>
|
||||
<Panel>
|
||||
<RealtimeLog data={data} />
|
||||
</GridRow>
|
||||
<GridRow columns="one-two">
|
||||
</Panel>
|
||||
</GridRow>
|
||||
<GridRow layout="one-two">
|
||||
<Panel>
|
||||
<RealtimeCountries data={countries} />
|
||||
</Panel>
|
||||
<Panel padding="0">
|
||||
<WorldMap data={countries} />
|
||||
</GridRow>
|
||||
</Grid>
|
||||
</>
|
||||
</Panel>
|
||||
</GridRow>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
.link:hover {
|
||||
cursor: pointer;
|
||||
color: var(--primary400);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.title {
|
||||
|
|
|
|||
|
|
@ -4,11 +4,12 @@ import { SessionsDataTable } from './SessionsDataTable';
|
|||
import { SessionsMetricsBar } from './SessionsMetricsBar';
|
||||
import { SessionProperties } from './SessionProperties';
|
||||
import { WorldMap } from '@/components/metrics/WorldMap';
|
||||
import { GridRow } from '@/components/layout/Grid';
|
||||
import { TabList, Tab, Tabs } from '@umami/react-zen';
|
||||
import { GridRow } from '@/components/layout/GridRow';
|
||||
import { TabList, Tab, Tabs, TabPanel } from '@umami/react-zen';
|
||||
import { useState } from 'react';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { SessionsWeekly } from './SessionsWeekly';
|
||||
import { Panel } from '@/components/layout/Panel';
|
||||
|
||||
export function SessionsPage({ websiteId }) {
|
||||
const [tab, setTab] = useState('activity');
|
||||
|
|
@ -18,22 +19,28 @@ export function SessionsPage({ websiteId }) {
|
|||
<>
|
||||
<WebsiteHeader websiteId={websiteId} />
|
||||
<SessionsMetricsBar websiteId={websiteId} />
|
||||
<GridRow columns="two-one">
|
||||
<WorldMap websiteId={websiteId} />
|
||||
<SessionsWeekly websiteId={websiteId} />
|
||||
<GridRow layout="two-one">
|
||||
<Panel padding="0">
|
||||
<WorldMap websiteId={websiteId} />
|
||||
</Panel>
|
||||
<Panel>
|
||||
<SessionsWeekly websiteId={websiteId} />
|
||||
</Panel>
|
||||
</GridRow>
|
||||
<Tabs
|
||||
selectedKey={tab}
|
||||
onSelectionChange={(value: any) => setTab(value)}
|
||||
style={{ marginBottom: 30 }}
|
||||
>
|
||||
<TabList>
|
||||
<Tab key="activity">{formatMessage(labels.activity)}</Tab>
|
||||
<Tab key="properties">{formatMessage(labels.properties)}</Tab>
|
||||
</TabList>
|
||||
</Tabs>
|
||||
{tab === 'activity' && <SessionsDataTable websiteId={websiteId} />}
|
||||
{tab === 'properties' && <SessionProperties websiteId={websiteId} />}
|
||||
<Panel marginY="6">
|
||||
<Tabs selectedKey={tab} onSelectionChange={(value: any) => setTab(value)}>
|
||||
<TabList>
|
||||
<Tab id="activity">{formatMessage(labels.activity)}</Tab>
|
||||
<Tab id="properties">{formatMessage(labels.properties)}</Tab>
|
||||
</TabList>
|
||||
<TabPanel id="activity">
|
||||
<SessionsDataTable websiteId={websiteId} />
|
||||
</TabPanel>
|
||||
<TabPanel id="properties">
|
||||
<SessionProperties websiteId={websiteId} />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</Panel>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
.cell {
|
||||
display: flex;
|
||||
background-color: var(--base75);
|
||||
background-color: var(--base-color-2);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: auto;
|
||||
|
|
@ -31,12 +31,12 @@
|
|||
|
||||
.hour {
|
||||
font-weight: 700;
|
||||
color: var(--font-color300);
|
||||
color: var(--font-color);
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.block {
|
||||
background-color: var(--primary400);
|
||||
background-color: var(--primary-color);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 100%;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { LoadingPanel } from '@/components/common/LoadingPanel';
|
|||
import { getDayOfWeekAsDate } from '@/lib/date';
|
||||
import styles from './SessionsWeekly.module.css';
|
||||
import classNames from 'classnames';
|
||||
import { Tooltip, TooltipTrigger } from '@umami/react-zen';
|
||||
import { Focusable, Tooltip, TooltipTrigger } from '@umami/react-zen';
|
||||
|
||||
export function SessionsWeekly({ websiteId }: { websiteId: string }) {
|
||||
const { data, ...props } = useWebsiteSessionsWeeklyQuery(websiteId);
|
||||
|
|
@ -67,12 +67,16 @@ export function SessionsWeekly({ websiteId }: { websiteId: string }) {
|
|||
return (
|
||||
<div key={j} className={classNames(styles.cell)}>
|
||||
{hour > 0 && (
|
||||
<TooltipTrigger>
|
||||
<div
|
||||
className={styles.block}
|
||||
style={{ opacity: pct, transform: `scale(${pct})` }}
|
||||
/>
|
||||
<Tooltip>{`${formatMessage(labels.visitors)}: ${hour}`}</Tooltip>
|
||||
<TooltipTrigger delay={0}>
|
||||
<Focusable>
|
||||
<div
|
||||
className={styles.block}
|
||||
style={{ opacity: pct, transform: `scale(${pct})` }}
|
||||
/>
|
||||
</Focusable>
|
||||
<Tooltip placement="right">{`${formatMessage(
|
||||
labels.visitors,
|
||||
)}: ${hour}`}</Tooltip>
|
||||
</TooltipTrigger>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { TextOverflow } from '@umami/react-zen';
|
||||
import { Text } from '@umami/react-zen';
|
||||
import { useMessages, useSessionDataQuery } from '@/components/hooks';
|
||||
import { Empty } from '@/components/common/Empty';
|
||||
import { DATA_TYPES } from '@/lib/constants';
|
||||
|
|
@ -19,7 +19,7 @@ export function SessionData({ websiteId, sessionId }: { websiteId: string; sessi
|
|||
<div key={dataKey}>
|
||||
<div className={styles.label}>
|
||||
<div className={styles.name}>
|
||||
<TextOverflow>{dataKey}</TextOverflow>
|
||||
<Text>{dataKey}</Text>
|
||||
</div>
|
||||
<div className={styles.type}>{DATA_TYPES[dataType]}</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
'use client';
|
||||
import { Grid, Row, Column } from '@umami/react-zen';
|
||||
import { Avatar } from '@/components/common/Avatar';
|
||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||
import { useWebsiteSessionQuery } from '@/components/hooks';
|
||||
import { WebsiteHeader } from '../../WebsiteHeader';
|
||||
import { WebsiteHeader } from '@/app/(main)/websites/[websiteId]/WebsiteHeader';
|
||||
import { SessionActivity } from './SessionActivity';
|
||||
import { SessionData } from './SessionData';
|
||||
import styles from './SessionDetailsPage.module.css';
|
||||
import { SessionInfo } from './SessionInfo';
|
||||
import { SessionStats } from './SessionStats';
|
||||
|
||||
|
|
@ -21,12 +21,17 @@ export function SessionDetailsPage({
|
|||
return (
|
||||
<LoadingPanel {...query} loadingIcon="spinner" data={data}>
|
||||
<WebsiteHeader websiteId={websiteId} />
|
||||
<div className={styles.page}>
|
||||
<div className={styles.sidebar}>
|
||||
<Avatar seed={data?.id} />
|
||||
<Grid
|
||||
gap="9"
|
||||
columns={{ xs: '1fr', sm: '1fr', md: '1fr 1fr', lg: '1fr 2fr 1fr', xl: '1fr 2fr 1fr' }}
|
||||
>
|
||||
<Column gap="6" maxWidth="200px">
|
||||
<Row justifyContent="center">
|
||||
<Avatar seed={data?.id} size={128} />
|
||||
</Row>
|
||||
<SessionInfo data={data} />
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
</Column>
|
||||
<Column gap="6">
|
||||
<SessionStats data={data} />
|
||||
<SessionActivity
|
||||
websiteId={websiteId}
|
||||
|
|
@ -34,11 +39,11 @@ export function SessionDetailsPage({
|
|||
startDate={data?.firstAt}
|
||||
endDate={data?.lastAt}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.data}>
|
||||
</Column>
|
||||
<Column gap="6">
|
||||
<SessionData websiteId={websiteId} sessionId={sessionId} />
|
||||
</div>
|
||||
</div>
|
||||
</Column>
|
||||
</Grid>
|
||||
</LoadingPanel>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { Icon, TextField, Column, Row, Label, Box, Text } from '@umami/react-zen';
|
||||
import { useFormat, useLocale, useMessages, useRegionNames, useTimezone } from '@/components/hooks';
|
||||
import { TypeIcon } from '@/components/common/TypeIcon';
|
||||
import { Icon, CopyIcon } from '@umami/react-zen';
|
||||
import { Icons } from '@/components/icons';
|
||||
import styles from './SessionInfo.module.css';
|
||||
|
||||
export function SessionInfo({ data }) {
|
||||
const { locale } = useLocale();
|
||||
|
|
@ -12,59 +11,73 @@ export function SessionInfo({ data }) {
|
|||
const { getRegionName } = useRegionNames(locale);
|
||||
|
||||
return (
|
||||
<div className={styles.info}>
|
||||
<dl>
|
||||
<dt>ID</dt>
|
||||
<dd>
|
||||
{data?.id} <CopyIcon value={data?.id} />
|
||||
</dd>
|
||||
<Column gap="6">
|
||||
<Box>
|
||||
<Label>ID</Label>
|
||||
<TextField value={data?.id} allowCopy />
|
||||
</Box>
|
||||
|
||||
<dt>{formatMessage(labels.lastSeen)}</dt>
|
||||
<dd>{formatTimezoneDate(data?.lastAt, 'PPPPpp')}</dd>
|
||||
<Box>
|
||||
<Label>{formatMessage(labels.lastSeen)}</Label>
|
||||
<Row>{formatTimezoneDate(data?.lastAt, 'PPPPpp')}</Row>
|
||||
</Box>
|
||||
|
||||
<dt>{formatMessage(labels.firstSeen)}</dt>
|
||||
<dd>{formatTimezoneDate(data?.firstAt, 'PPPPpp')}</dd>
|
||||
<Box>
|
||||
<Label>{formatMessage(labels.firstSeen)}</Label>
|
||||
<Row>{formatTimezoneDate(data?.firstAt, 'PPPPpp')}</Row>
|
||||
</Box>
|
||||
|
||||
<dt>{formatMessage(labels.country)}</dt>
|
||||
<dd>
|
||||
<Box>
|
||||
<Label>{formatMessage(labels.country)}</Label>
|
||||
<Row gap="3">
|
||||
<TypeIcon type="country" value={data?.country} />
|
||||
{formatValue(data?.country, 'country')}
|
||||
</dd>
|
||||
<Text>{formatValue(data?.country, 'country')}</Text>
|
||||
</Row>
|
||||
</Box>
|
||||
|
||||
<dt>{formatMessage(labels.region)}</dt>
|
||||
<dd>
|
||||
<Box>
|
||||
<Label>{formatMessage(labels.region)}</Label>
|
||||
<Row gap="3">
|
||||
<Icon>
|
||||
<Icons.Location />
|
||||
</Icon>
|
||||
{getRegionName(data?.subdivision1)}
|
||||
</dd>
|
||||
<Text>{getRegionName(data?.subdivision1)}</Text>
|
||||
</Row>
|
||||
</Box>
|
||||
|
||||
<dt>{formatMessage(labels.city)}</dt>
|
||||
<dd>
|
||||
<Box>
|
||||
<Label>{formatMessage(labels.city)}</Label>
|
||||
<Row gap="3">
|
||||
<Icon>
|
||||
<Icons.Location />
|
||||
</Icon>
|
||||
{data?.city}
|
||||
</dd>
|
||||
<Text>{data?.city}</Text>
|
||||
</Row>
|
||||
</Box>
|
||||
|
||||
<dt>{formatMessage(labels.os)}</dt>
|
||||
<dd>
|
||||
<Box>
|
||||
<Label>{formatMessage(labels.os)}</Label>
|
||||
<Row gap="3">
|
||||
<TypeIcon type="os" value={data?.os?.toLowerCase()?.replaceAll(/\W/g, '-')} />
|
||||
{formatValue(data?.os, 'os')}
|
||||
</dd>
|
||||
<Text>{formatValue(data?.os, 'os')}</Text>
|
||||
</Row>
|
||||
</Box>
|
||||
|
||||
<dt>{formatMessage(labels.device)}</dt>
|
||||
<dd>
|
||||
<Box>
|
||||
<Label>{formatMessage(labels.device)}</Label>
|
||||
<Row gap="3">
|
||||
<TypeIcon type="device" value={data?.device} />
|
||||
{formatValue(data?.device, 'device')}
|
||||
</dd>
|
||||
<Text>{formatValue(data?.device, 'device')}</Text>
|
||||
</Row>
|
||||
</Box>
|
||||
|
||||
<dt>{formatMessage(labels.browser)}</dt>
|
||||
<dd>
|
||||
<Box>
|
||||
<Label>{formatMessage(labels.browser)}</Label>
|
||||
<Row gap="3">
|
||||
<TypeIcon type="browser" value={data?.browser} />
|
||||
{formatValue(data?.browser, 'browser')}
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<Text>{formatValue(data?.browser, 'browser')}</Text>
|
||||
</Row>
|
||||
</Box>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue