mirror of
https://github.com/umami-software/umami.git
synced 2026-02-06 21:57:16 +01:00
Panels redesign.
This commit is contained in:
parent
7886c3f393
commit
f5bc3dc6c2
58 changed files with 530 additions and 733 deletions
|
|
@ -17,5 +17,5 @@ export function Avatar({ seed, size = 128, ...props }: { seed: string; size?: nu
|
|||
}).toDataUri();
|
||||
}, []);
|
||||
|
||||
return <img src={avatar} alt="Avatar" style={{ borderRadius: '100%' }} />;
|
||||
return <img src={avatar} alt="Avatar" style={{ borderRadius: '100%', width: size }} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { Loading, SearchField, Row, Column } from '@umami/react-zen';
|
|||
import { useMessages, useNavigation } from '@/components/hooks';
|
||||
import { Empty } from '@/components/common/Empty';
|
||||
import { Pager } from '@/components/common/Pager';
|
||||
import { Panel } from '@/components/layout/Panel';
|
||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||
import { PagedQueryResult } from '@/lib/types';
|
||||
|
||||
|
|
@ -60,14 +59,12 @@ export function DataGrid({
|
|||
</Row>
|
||||
)}
|
||||
<LoadingPanel data={data} isLoading={isLoading} isFetched={isFetched} error={error}>
|
||||
<Panel>
|
||||
<Column>
|
||||
{hasData ? (typeof children === 'function' ? children(result) : children) : null}
|
||||
{isLoading && <Loading position="page" />}
|
||||
{!isLoading && !hasData && !search && (renderEmpty ? renderEmpty() : <Empty />)}
|
||||
{!isLoading && noResults && <Empty message={formatMessage(messages.noResultsFound)} />}
|
||||
</Column>
|
||||
</Panel>
|
||||
<Column>
|
||||
{hasData ? (typeof children === 'function' ? children(result) : children) : null}
|
||||
{isLoading && <Loading position="page" />}
|
||||
{!isLoading && !hasData && !search && (renderEmpty ? renderEmpty() : <Empty />)}
|
||||
{!isLoading && noResults && <Empty message={formatMessage(messages.noResultsFound)} />}
|
||||
</Column>
|
||||
{allowPaging && hasData && (
|
||||
<Row marginTop="6">
|
||||
<Pager page={page} pageSize={pageSize} count={count} onPageChange={handlePageChange} />
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
|
||||
.button.primary {
|
||||
color: var(--light50);
|
||||
background: var(--primary400);
|
||||
background: var(--primary-color);
|
||||
}
|
||||
|
||||
.button.primary:hover {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { Row } from '@umami/react-zen';
|
||||
|
||||
export function TypeIcon({
|
||||
type,
|
||||
|
|
@ -10,7 +11,7 @@ export function TypeIcon({
|
|||
children?: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<Row gap="3" alignItems="center">
|
||||
<img
|
||||
src={`${process.env.basePath || ''}/images/${type}/${value
|
||||
?.replaceAll(' ', '-')
|
||||
|
|
@ -23,6 +24,6 @@ export function TypeIcon({
|
|||
height={type === 'country' ? undefined : 16}
|
||||
/>
|
||||
{children}
|
||||
</>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
1
src/components/declarations.d.ts
vendored
1
src/components/declarations.d.ts
vendored
|
|
@ -1,4 +1,5 @@
|
|||
declare module '*.css';
|
||||
declare module '*.svg';
|
||||
declare module '*.json';
|
||||
declare module 'react-simple-maps';
|
||||
declare module 'uuid';
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export function useNavigation() {
|
|||
const params = useSearchParams();
|
||||
const [, teamId] = pathname.match(/^\/teams\/([a-f0-9-]+)/) || [];
|
||||
|
||||
const query = useMemo(() => {
|
||||
const query = useMemo<{ [key: string]: any }>(() => {
|
||||
const obj = {};
|
||||
|
||||
for (const [key, value] of params.entries()) {
|
||||
|
|
|
|||
8
src/components/hooks/useWebsite.ts
Normal file
8
src/components/hooks/useWebsite.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { WebsiteContext } from '@/app/(main)/websites/[websiteId]/WebsiteProvider';
|
||||
import { useContext } from 'react';
|
||||
|
||||
export function useWebsite() {
|
||||
const website = useContext(WebsiteContext);
|
||||
|
||||
return website;
|
||||
}
|
||||
|
|
@ -5,8 +5,6 @@ import { DatePickerForm } from '@/components/metrics/DatePickerForm';
|
|||
import { useLocale, useMessages } from '@/components/hooks';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { formatDate } from '@/lib/date';
|
||||
import styles from './DateFilter.module.css';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export interface DateFilterProps {
|
||||
value: string;
|
||||
|
|
@ -23,11 +21,8 @@ export function DateFilter({
|
|||
startDate,
|
||||
endDate,
|
||||
value,
|
||||
offset = 0,
|
||||
className,
|
||||
onChange,
|
||||
showAllTime = false,
|
||||
alignment = 'end',
|
||||
}: DateFilterProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const [showPicker, setShowPicker] = useState(false);
|
||||
|
|
@ -38,19 +33,19 @@ export function DateFilter({
|
|||
label: formatMessage(labels.lastHours, { x: 24 }),
|
||||
value: '24hour',
|
||||
},
|
||||
{ divider: true },
|
||||
{
|
||||
label: formatMessage(labels.thisWeek),
|
||||
value: '0week',
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
label: formatMessage(labels.lastDays, { x: 7 }),
|
||||
value: '7day',
|
||||
},
|
||||
{ divider: true },
|
||||
{
|
||||
label: formatMessage(labels.thisMonth),
|
||||
value: '0month',
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
label: formatMessage(labels.lastDays, { x: 30 }),
|
||||
|
|
@ -60,25 +55,25 @@ export function DateFilter({
|
|||
label: formatMessage(labels.lastDays, { x: 90 }),
|
||||
value: '90day',
|
||||
},
|
||||
{ divider: true },
|
||||
{ label: formatMessage(labels.thisYear), value: '0year' },
|
||||
{
|
||||
label: formatMessage(labels.lastMonths, { x: 6 }),
|
||||
value: '6month',
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
label: formatMessage(labels.lastMonths, { x: 12 }),
|
||||
value: '12month',
|
||||
},
|
||||
{ divider: true },
|
||||
showAllTime && {
|
||||
label: formatMessage(labels.allTime),
|
||||
value: 'all',
|
||||
divider: true,
|
||||
},
|
||||
{ divider: true },
|
||||
{
|
||||
label: formatMessage(labels.customRange),
|
||||
value: 'custom',
|
||||
divider: true,
|
||||
},
|
||||
]
|
||||
.filter(n => n)
|
||||
|
|
@ -97,50 +92,21 @@ export function DateFilter({
|
|||
onChange(value.toString());
|
||||
};
|
||||
|
||||
/*
|
||||
const handleClose = () => setShowPicker(false);
|
||||
|
||||
const renderValue = (value: string) => {
|
||||
const { unit } = parseDateValue(value) || {};
|
||||
|
||||
if (offset && unit === 'year') {
|
||||
return formatDate(startDate, 'yyyy', locale);
|
||||
}
|
||||
|
||||
if (offset && unit === 'month') {
|
||||
return formatDate(startDate, 'MMMM yyyy', locale);
|
||||
}
|
||||
|
||||
if (value.startsWith('range') || offset) {
|
||||
return (
|
||||
<CustomRange
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
unit={unit}
|
||||
onClick={() => handleChange('custom')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return options.find(e => e.value === value)?.label;
|
||||
};*/
|
||||
|
||||
return (
|
||||
<>
|
||||
<Select
|
||||
className={classNames(className, styles.dropdown)}
|
||||
items={options}
|
||||
value={value}
|
||||
placeholder={formatMessage(labels.selectDate)}
|
||||
onSelectionChange={handleChange}
|
||||
>
|
||||
{({ label, value, divider }: any) => {
|
||||
if (divider) {
|
||||
return <ListSeparator />;
|
||||
}
|
||||
|
||||
return <ListItem id={value}>{label}</ListItem>;
|
||||
}}
|
||||
{options.map(({ label, value, divider }: any) => {
|
||||
return (
|
||||
<>
|
||||
{divider && <ListSeparator />}
|
||||
<ListItem id={value}>{label}</ListItem>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
{showPicker && (
|
||||
<Modal isOpen={true}>
|
||||
|
|
@ -160,7 +126,7 @@ export function DateFilter({
|
|||
);
|
||||
}
|
||||
|
||||
const CustomRange = ({ startDate, endDate, unit, onClick }) => {
|
||||
export const CustomRange = ({ startDate, endDate, unit, onClick }) => {
|
||||
const { locale } = useLocale();
|
||||
|
||||
const monthFormat = unit === 'month';
|
||||
|
|
|
|||
|
|
@ -1,76 +0,0 @@
|
|||
.grid {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
border-top: 1px solid var(--base300);
|
||||
}
|
||||
|
||||
.row.compare {
|
||||
grid-template-columns: max-content 1fr 1fr;
|
||||
}
|
||||
|
||||
.col {
|
||||
padding: 20px;
|
||||
min-height: 430px;
|
||||
border-inline-start: 1px solid var(--base300);
|
||||
}
|
||||
|
||||
.col:first-child {
|
||||
border-inline-start: 0;
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
.col:last-child {
|
||||
padding-inline-end: 0;
|
||||
}
|
||||
|
||||
.col.one {
|
||||
grid-column: span 6;
|
||||
}
|
||||
|
||||
.col.two {
|
||||
grid-column: span 3;
|
||||
}
|
||||
|
||||
.col.three {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.col.two-one:first-child {
|
||||
grid-column: span 4;
|
||||
}
|
||||
|
||||
.col.two-one:last-child {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.col.one-two:first-child {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.col.one-two:last-child {
|
||||
grid-column: span 4;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 992px) {
|
||||
.row {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.row > .col {
|
||||
border-top: 1px solid var(--base300);
|
||||
border-inline-start: 0;
|
||||
border-inline-end: 0;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.col.two,
|
||||
.col.three,
|
||||
.col.one-two,
|
||||
.col.two-one {
|
||||
grid-column: span 6 !important;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import { CSSProperties } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { mapChildren } from '@/lib/react';
|
||||
// eslint-disable-next-line css-modules/no-unused-class
|
||||
import styles from './Grid.module.css';
|
||||
|
||||
export interface GridProps {
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
children?: any;
|
||||
}
|
||||
|
||||
export function Grid({ className, style, children }: GridProps) {
|
||||
return (
|
||||
<div className={classNames(styles.grid, className)} style={style}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function GridRow(props: {
|
||||
[x: string]: any;
|
||||
columns?: 'one' | 'two' | 'three' | 'one-two' | 'two-one' | 'compare';
|
||||
className?: string;
|
||||
children?: any;
|
||||
}) {
|
||||
const { columns = 'two', className, children, ...otherProps } = props;
|
||||
return (
|
||||
<div {...otherProps} className={classNames(styles.row, className, { [styles[columns]]: true })}>
|
||||
{mapChildren(children, child => {
|
||||
return <div className={classNames(styles.col, { [styles[columns]]: true })}>{child}</div>;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
23
src/components/layout/GridRow.tsx
Normal file
23
src/components/layout/GridRow.tsx
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { Grid } from '@umami/react-zen';
|
||||
|
||||
const LAYOUTS = {
|
||||
one: { columns: '1fr' },
|
||||
two: { columns: { xs: '1fr', sm: '1fr', md: '1fr 1fr', lg: '1fr 1fr' } },
|
||||
three: { columns: { xs: '1fr', sm: '1fr', md: '1fr 1fr 1fr', lg: '1fr 2fr' } },
|
||||
'one-two': { columns: { xs: '1fr', sm: '1fr', md: '1fr 2fr', lg: '1fr 2fr' } },
|
||||
'two-one': { columns: { xs: '1fr', sm: '1fr', md: '2fr 1fr', lg: '2fr 1fr', xl: '2fr 1fr' } },
|
||||
};
|
||||
|
||||
export function GridRow(props: {
|
||||
[x: string]: any;
|
||||
layout?: 'one' | 'two' | 'three' | 'one-two' | 'two-one' | 'compare';
|
||||
className?: string;
|
||||
children?: any;
|
||||
}) {
|
||||
const { layout = 'two', children, ...otherProps } = props;
|
||||
return (
|
||||
<Grid gap="3" {...LAYOUTS[layout]} {...otherProps}>
|
||||
{children}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
.layout {
|
||||
display: grid;
|
||||
grid-template-columns: max-content 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.menu {
|
||||
width: 240px;
|
||||
padding-top: 34px;
|
||||
padding-inline-end: 20px;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 50vh;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 992px) {
|
||||
.layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { Grid, Column } from '@umami/react-zen';
|
||||
import { MenuNav } from '@/components/layout/MenuNav';
|
||||
|
||||
export function MenuLayout({ items = [], children }: { items: any[]; children: ReactNode }) {
|
||||
const cloudMode = !!process.env.cloudMode;
|
||||
|
||||
return (
|
||||
<Grid columns="auto 1fr" gap="5">
|
||||
{!cloudMode && (
|
||||
<Column width="240px">
|
||||
<MenuNav items={items} shallow={true} />
|
||||
</Column>
|
||||
)}
|
||||
<Column>{children}</Column>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
.menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.item a {
|
||||
color: var(--font-color100);
|
||||
flex: 1;
|
||||
padding: var(--size300) var(--size600);
|
||||
}
|
||||
|
||||
.item {
|
||||
padding: 0;
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.selected {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
import { Column, Button, Text, List, ListItem } from '@umami/react-zen';
|
||||
import Link from 'next/link';
|
||||
|
||||
export interface MenuNavProps {
|
||||
items: any[];
|
||||
selectedKey?: string;
|
||||
}
|
||||
|
||||
export function MenuNav({ items, selectedKey }: MenuNavProps) {
|
||||
return (
|
||||
<List>
|
||||
{items.map(({ key, label, url }) => {
|
||||
return (
|
||||
<ListItem key={key} href={url}>
|
||||
<Text weight={key === selectedKey ? 'bold' : 'regular'}>{label}</Text>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
);
|
||||
}
|
||||
|
||||
export function MenuNav2({ items, selectedKey }: MenuNavProps) {
|
||||
return (
|
||||
<Column gap="3" alignItems="flex-start" justifyContent="stretch">
|
||||
{items.map(({ key, label, url }) => {
|
||||
return (
|
||||
<Button key={key} style={{ width: '100%' }} asChild>
|
||||
<Link href={url}>
|
||||
<Text weight={key === selectedKey ? 'bold' : 'regular'}>{label}</Text>
|
||||
</Link>
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
@ -25,17 +25,7 @@ export function Page({
|
|||
}
|
||||
|
||||
return (
|
||||
<Column
|
||||
{...props}
|
||||
gridColumn="2 / 3"
|
||||
gridRow="2 / 3"
|
||||
marginRight="6"
|
||||
marginBottom="6"
|
||||
width="100%"
|
||||
maxWidth="1320px"
|
||||
minHeight="600px"
|
||||
margin="auto"
|
||||
>
|
||||
<Column {...props} width="100%" maxWidth="1320px" margin="auto" paddingBottom="9">
|
||||
{children}
|
||||
</Column>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export function PageHeader({
|
|||
children?: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Row justifyContent="space-between" alignItems="center" paddingBottom="6">
|
||||
<Row justifyContent="space-between" alignItems="center">
|
||||
<Row gap="3">
|
||||
{icon && <Icon size="lg">{icon}</Icon>}
|
||||
{title && <Heading>{title}</Heading>}
|
||||
|
|
|
|||
|
|
@ -2,5 +2,14 @@ import { Box } from '@umami/react-zen';
|
|||
import type { BoxProps } from '@umami/react-zen/Box';
|
||||
|
||||
export function Panel(props: BoxProps) {
|
||||
return <Box padding="6" borderSize="1" borderRadius="3" backgroundColor="solid" {...props} />;
|
||||
return (
|
||||
<Box
|
||||
padding="6"
|
||||
borderSize="1"
|
||||
borderRadius="3"
|
||||
backgroundColor="solid"
|
||||
shadow="4"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
20
src/components/layout/SideBar.tsx
Normal file
20
src/components/layout/SideBar.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { Text, List, ListItem } from '@umami/react-zen';
|
||||
|
||||
export interface MenuNavProps {
|
||||
items: any[];
|
||||
selectedKey?: string;
|
||||
}
|
||||
|
||||
export function SideBar({ items, selectedKey }: MenuNavProps) {
|
||||
return (
|
||||
<List>
|
||||
{items.map(({ key, label, url }) => {
|
||||
return (
|
||||
<ListItem key={key} href={url}>
|
||||
<Text weight={key === selectedKey ? 'bold' : 'regular'}>{label}</Text>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
);
|
||||
}
|
||||
|
|
@ -2,8 +2,7 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
font-size: 12px;
|
||||
padding: 0.1em 0.5em;
|
||||
border-radius: 5px;
|
||||
color: var(--base500);
|
||||
|
|
@ -11,16 +10,16 @@
|
|||
}
|
||||
|
||||
.positive {
|
||||
color: var(--green700);
|
||||
background: var(--green100);
|
||||
color: var(--success-color);
|
||||
background: color-mix(in srgb, var(--success-color), var(--background-color) 85%);
|
||||
}
|
||||
|
||||
.negative {
|
||||
color: var(--red700);
|
||||
background: var(--red100);
|
||||
color: var(--danger-color);
|
||||
background: color-mix(in srgb, var(--danger-color), var(--background-color) 85%);
|
||||
}
|
||||
|
||||
.neutral {
|
||||
color: var(--base700);
|
||||
background: var(--base100);
|
||||
color: var(--font-color-muted);
|
||||
background: var(--base-color-2);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
}
|
||||
|
||||
.row:hover {
|
||||
background-color: var(--base75);
|
||||
background-color: var(--base-color-2);
|
||||
}
|
||||
|
||||
.label {
|
||||
|
|
@ -60,7 +60,7 @@
|
|||
}
|
||||
|
||||
.label a:hover {
|
||||
color: var(--primary400);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.label:empty {
|
||||
|
|
@ -83,8 +83,8 @@
|
|||
.percent {
|
||||
position: relative;
|
||||
width: 50px;
|
||||
color: var(--base600);
|
||||
border-inline-start: 1px solid var(--base600);
|
||||
color: var(--base-color-9);
|
||||
border-inline-start: 1px solid var(--base-color-9);
|
||||
padding-inline-start: 10px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
|
@ -95,7 +95,7 @@
|
|||
left: 0;
|
||||
height: 30px;
|
||||
opacity: 0.1;
|
||||
background: var(--primary400);
|
||||
background: var(--primary-color);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { FixedSizeList } from 'react-window';
|
||||
import { useSpring, config } from '@react-spring/web';
|
||||
import classNames from 'classnames';
|
||||
|
|
@ -6,7 +7,6 @@ import { Empty } from '@/components/common/Empty';
|
|||
import { formatLongNumber } from '@/lib/format';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import styles from './ListTable.module.css';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
const ITEM_SIZE = 30;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
.container {
|
||||
position: relative;
|
||||
min-height: 430px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.search {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 992px) {
|
||||
.container {
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.search {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { ReactNode, useMemo, useState } from 'react';
|
||||
import { Loading, Icon, Text, SearchField, Row } from '@umami/react-zen';
|
||||
import { Loading, Icon, Text, SearchField, Row, Column } from '@umami/react-zen';
|
||||
import { ErrorMessage } from '@/components/common/ErrorMessage';
|
||||
import { LinkButton } from '@/components/common/LinkButton';
|
||||
import { DEFAULT_ANIMATION_DURATION } from '@/lib/constants';
|
||||
|
|
@ -7,8 +7,6 @@ import { percentFilter } from '@/lib/filters';
|
|||
import { useNavigation, useWebsiteMetricsQuery, useMessages, useFormat } from '@/components/hooks';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { ListTable, ListTableProps } from './ListTable';
|
||||
import styles from './MetricsTable.module.css';
|
||||
import { Panel } from '@/components/layout/Panel';
|
||||
|
||||
export interface MetricsTableProps extends ListTableProps {
|
||||
websiteId: string;
|
||||
|
|
@ -85,20 +83,12 @@ export function MetricsTable({
|
|||
}, [data, dataFilter, search, limit, formatValue, type]);
|
||||
|
||||
return (
|
||||
<Panel>
|
||||
<Column gap="3" justifyContent="space-between">
|
||||
{error && <ErrorMessage />}
|
||||
<div className={styles.actions}>
|
||||
{allowSearch && (
|
||||
<SearchField
|
||||
className={styles.search}
|
||||
value={search}
|
||||
onSearch={setSearch}
|
||||
delay={300}
|
||||
autoFocus={true}
|
||||
/>
|
||||
)}
|
||||
<Row alignItems="center" justifyContent="space-between">
|
||||
{allowSearch && <SearchField value={search} onSearch={setSearch} delay={300} />}
|
||||
{children}
|
||||
</div>
|
||||
</Row>
|
||||
{data && !error && (
|
||||
<ListTable {...(props as ListTableProps)} data={filteredData} className={className} />
|
||||
)}
|
||||
|
|
@ -113,6 +103,6 @@ export function MetricsTable({
|
|||
</LinkButton>
|
||||
)}
|
||||
</Row>
|
||||
</Panel>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { useMemo } from 'react';
|
|||
import { BarChart, BarChartProps } from '@/components/charts/BarChart';
|
||||
import { useLocale, useTheme, useMessages } from '@/components/hooks';
|
||||
import { renderDateLabels } from '@/lib/charts';
|
||||
import { Panel } from '@/components/layout/Panel';
|
||||
|
||||
export interface PageviewsChartProps extends BarChartProps {
|
||||
data: {
|
||||
|
|
@ -77,15 +76,13 @@ export function PageviewsChart({
|
|||
}, [data, locale]);
|
||||
|
||||
return (
|
||||
<Panel>
|
||||
<BarChart
|
||||
{...props}
|
||||
data={chartData}
|
||||
unit={unit}
|
||||
isLoading={isLoading}
|
||||
isAllTime={isAllTime}
|
||||
renderXLabel={renderDateLabels(unit, locale)}
|
||||
/>
|
||||
</Panel>
|
||||
<BarChart
|
||||
{...props}
|
||||
data={chartData}
|
||||
unit={unit}
|
||||
isLoading={isLoading}
|
||||
isAllTime={isAllTime}
|
||||
renderXLabel={renderDateLabels(unit, locale)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
.param {
|
||||
padding: 0 8px;
|
||||
color: var(--primary400);
|
||||
color: var(--primary-color);
|
||||
background: var(--blue100);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export function ReferrersTable({ allowFilter, ...props }: ReferrersTableProps) {
|
|||
return `(${formatMessage(labels.other)})`;
|
||||
} else {
|
||||
return (
|
||||
<Flexbox alignItems="center" gap={10}>
|
||||
<Flexbox alignItems="center" gap="3">
|
||||
<Favicon domain={referrer} />
|
||||
{GROUPED_DOMAINS.find(({ domain }) => domain === referrer)?.name}
|
||||
</Flexbox>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,20 @@
|
|||
import { FloatingTooltip, Column } from '@umami/react-zen';
|
||||
import { useState, useMemo, HTMLAttributes } from 'react';
|
||||
import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps';
|
||||
import classNames from 'classnames';
|
||||
import { colord } from 'colord';
|
||||
import { ISO_COUNTRIES, MAP_FILE } from '@/lib/constants';
|
||||
import { useDateRange, useTheme, useWebsiteMetricsQuery } from '@/components/hooks';
|
||||
import { useCountryNames } from '@/components/hooks';
|
||||
import { useLocale } from '@/components/hooks';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import {
|
||||
useDateRange,
|
||||
useTheme,
|
||||
useWebsiteMetricsQuery,
|
||||
useCountryNames,
|
||||
useLocale,
|
||||
useMessages,
|
||||
} from '@/components/hooks';
|
||||
import { formatLongNumber } from '@/lib/format';
|
||||
import { percentFilter } from '@/lib/filters';
|
||||
import styles from './WorldMap.module.css';
|
||||
import { FloatingTooltip } from '@umami/react-zen';
|
||||
|
||||
export function WorldMap({
|
||||
websiteId,
|
||||
|
|
@ -70,7 +74,7 @@ export function WorldMap({
|
|||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
<Column
|
||||
{...props}
|
||||
className={classNames(styles.container, className)}
|
||||
data-tip=""
|
||||
|
|
@ -105,6 +109,6 @@ export function WorldMap({
|
|||
</ZoomableGroup>
|
||||
</ComposableMap>
|
||||
{tooltip && <FloatingTooltip>{tooltip}</FloatingTooltip>}
|
||||
</div>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue