Panels redesign.

This commit is contained in:
Mike Cao 2025-03-25 14:47:51 -07:00
parent 7886c3f393
commit f5bc3dc6c2
58 changed files with 530 additions and 733 deletions

View file

@ -11,7 +11,7 @@
.link:hover {
cursor: pointer;
color: var(--primary400);
color: var(--primary-color);
}
.title {

View file

@ -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>
</>
);
}

View file

@ -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%;

View file

@ -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>

View file

@ -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>

View file

@ -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>
);
}

View file

@ -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>
);
}