Updated reports components.

This commit is contained in:
Mike Cao 2025-03-26 21:54:23 -07:00
parent f5bc3dc6c2
commit 0f6cdf8b80
95 changed files with 580 additions and 698 deletions

View file

@ -1,7 +1,9 @@
import { Column } from '@umami/react-zen';
export interface BoardProps {}
export function Board(props: BoardProps) {
return <Column>{}</Column>;
export interface BoardProps {
children?: React.ReactNode;
}
export function Board({ children }: BoardProps) {
return <Column>{children}</Column>;
}

View file

@ -26,7 +26,6 @@ export function Chart({
onCreate,
onUpdate,
onTooltip,
className,
chartOptions,
tooltip,
...props

View file

@ -2,7 +2,7 @@ import { Row, Column, Text } from '@umami/react-zen';
export function ActionForm({ label, description, children }) {
return (
<Row padding="6" borderSize="1" borderRadius="3" justifyContent="space-between" shadow="2">
<Row padding="6" border borderRadius="3" justifyContent="space-between" shadow="2">
<Column>
<Text weight="bold">{label}</Text>
<Text>{description}</Text>

View file

@ -1,21 +1,25 @@
import { ReactNode } from 'react';
import { Heading, Icon, Row } from '@umami/react-zen';
import { Heading, Icon, Row, Text } from '@umami/react-zen';
export function PageHeader({
title,
description,
icon,
children,
}: {
title?: ReactNode;
title: string;
description?: string;
icon?: ReactNode;
allowEdit?: boolean;
className?: string;
children?: ReactNode;
}) {
return (
<Row justifyContent="space-between" alignItems="center">
<Row justifyContent="space-between" alignItems="center" marginY="6">
<Row gap="3">
{icon && <Icon size="lg">{icon}</Icon>}
{title && <Heading>{title}</Heading>}
{title && <Heading size="2">{title}</Heading>}
{description && <Text color="muted">{description}</Text>}
</Row>
<Row justifyContent="flex-end">{children}</Row>
</Row>

View file

@ -0,0 +1,6 @@
import { Box } from '@umami/react-zen';
import type { BoxProps } from '@umami/react-zen/Box';
export function Panel(props: BoxProps) {
return <Box padding="6" border borderRadius="3" backgroundColor="solid" shadow="4" {...props} />;
}

View file

@ -41,6 +41,7 @@ export * from './useMessages';
export * from './useModified';
export * from './usePagedQuery';
export * from './useRegionNames';
export * from './useReport';
export * from './useSticky';
export * from './useNavigation';
export * from './useTheme';

View file

@ -0,0 +1,6 @@
import { useContext } from 'react';
import { ReportContext } from '@/app/(main)/reports/[reportId]/Report';
export function useReport() {
return useContext(ReportContext);
}

View file

@ -8,6 +8,7 @@ const selector = (state: { theme: string }) => state.theme;
export function useTheme() {
const theme = useApp(selector) || getItem(THEME_CONFIG) || DEFAULT_THEME;
const { primary, text, line, fill } = THEME_COLORS[theme];
const primaryColor = colord(THEME_COLORS[theme].primary);
const colors = useMemo(() => {
@ -16,8 +17,8 @@ export function useTheme() {
...THEME_COLORS[theme],
},
chart: {
text: THEME_COLORS[theme].gray700,
line: THEME_COLORS[theme].gray200,
text,
line,
views: {
hoverBackgroundColor: primaryColor.alpha(0.7).toRgbString(),
backgroundColor: primaryColor.alpha(0.4).toRgbString(),
@ -32,10 +33,10 @@ export function useTheme() {
},
},
map: {
baseColor: THEME_COLORS[theme].primary,
fillColor: THEME_COLORS[theme].gray100,
strokeColor: THEME_COLORS[theme].primary,
hoverColor: THEME_COLORS[theme].primary,
baseColor: primary,
fillColor: fill,
strokeColor: primary,
hoverColor: primary,
},
};
}, [theme]);

View file

@ -103,7 +103,9 @@ export function DateFilter({
return (
<>
{divider && <ListSeparator />}
<ListItem id={value}>{label}</ListItem>
<ListItem key={value} id={value}>
{label}
</ListItem>
</>
);
})}

View file

@ -1,9 +1,8 @@
import { useDateRange } from '@/components/hooks';
import { isAfter } from 'date-fns';
import { getOffsetDateRange } from '@/lib/date';
import { Button, Icon, Icons } from '@umami/react-zen';
import { Button, Icon, Icons, Row } from '@umami/react-zen';
import { DateFilter } from './DateFilter';
import styles from './WebsiteDateFilter.module.css';
import { DateRange } from '@/lib/types';
export function WebsiteDateFilter({
@ -27,9 +26,22 @@ export function WebsiteDateFilter({
};
return (
<div className={styles.container}>
<Row gap="3">
{value !== 'all' && !value.startsWith('range') && (
<Row gap="1">
<Button onPress={() => handleIncrement(-1)} variant="quiet">
<Icon size="xs" rotate={180}>
<Icons.Chevron />
</Icon>
</Button>
<Button onPress={() => handleIncrement(1)} variant="quiet" isDisabled={disableForward}>
<Icon size="xs">
<Icons.Chevron />
</Icon>
</Button>
</Row>
)}
<DateFilter
className={styles.dropdown}
value={value}
startDate={startDate}
endDate={endDate}
@ -37,20 +49,6 @@ export function WebsiteDateFilter({
onChange={handleChange}
showAllTime={showAllTime}
/>
{value !== 'all' && !value.startsWith('range') && (
<div className={styles.buttons}>
<Button onPress={() => handleIncrement(-1)}>
<Icon size="sm" rotate={180}>
<Icons.Chevron />
</Icon>
</Button>
<Button onPress={() => handleIncrement(1)} isDisabled={disableForward}>
<Icon size="sm">
<Icons.Chevron />
</Icon>
</Button>
</div>
)}
</div>
</Row>
);
}

View file

@ -1,15 +0,0 @@
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"
shadow="4"
{...props}
/>
);
}

View file

@ -9,18 +9,12 @@ import {
useFilters,
} from '@/components/hooks';
import { FieldFilterEditForm } from '@/app/(main)/reports/[reportId]/FieldFilterEditForm';
import { OPERATOR_PREFIXES } from '@/lib/constants';
import { FILTER_COLUMNS, OPERATOR_PREFIXES } from '@/lib/constants';
import { isSearchOperator, parseParameterValue } from '@/lib/params';
import styles from './FilterTags.module.css';
import { WebsiteFilterButton } from '@/app/(main)/websites/[websiteId]/WebsiteFilterButton';
export function FilterTags({
websiteId,
params,
}: {
websiteId: string;
params: { [key: string]: string };
}) {
export function FilterTags({ websiteId }: { websiteId: string }) {
const { formatMessage, labels } = useMessages();
const { formatValue } = useFormat();
const { dateRange } = useDateRange(websiteId);
@ -32,6 +26,14 @@ export function FilterTags({
const { fields } = useFields();
const { operatorLabels } = useFilters();
const { startDate, endDate } = dateRange;
const { query } = useNavigation();
const params = Object.keys(query).reduce((obj, key) => {
if (FILTER_COLUMNS[key]) {
obj[key] = query[key];
}
return obj;
}, {});
if (Object.keys(params).filter(key => params[key]).length === 0) {
return null;
@ -60,13 +62,13 @@ export function FilterTags({
return (
<Row
gap="3"
backgroundColor="1"
backgroundColor="2"
alignItems="center"
paddingX="3"
paddingY="2"
paddingX="5"
paddingY="3"
border
borderRadius="2"
borderSize="1"
marginBottom="6"
wrap="wrap"
>
<Text weight="bold">{formatMessage(labels.filters)}</Text>
{Object.keys(params).map(key => {

View file

@ -1,37 +1,8 @@
.card {
display: flex;
flex-direction: column;
justify-content: center;
min-width: 150px;
}
.card.compare .change {
font-size: 16px;
margin: 10px 0;
}
.card:first-child {
padding-left: 0;
border-right: 1px solid var(--border-color);
padding: 0 50px;
}
.card:last-child {
border: 0;
}
.value {
font-size: 36px;
font-weight: 700;
white-space: nowrap;
color: var(--base900);
line-height: 1.5;
}
.value.prev {
color: var(--base800);
}
.label {
font-weight: 700;
white-space: nowrap;
color: var(--base800);
border-right: 0;
}

View file

@ -1,4 +1,4 @@
import classNames from 'classnames';
import { Text, Column } from '@umami/react-zen';
import { useSpring } from '@react-spring/web';
import { formatNumber } from '@/lib/format';
import { AnimatedDiv } from '@/components/common/AnimatedDiv';
@ -15,7 +15,6 @@ export interface MetricCardProps {
showLabel?: boolean;
showChange?: boolean;
showPrevious?: boolean;
className?: string;
}
export const MetricCard = ({
@ -27,7 +26,6 @@ export const MetricCard = ({
showLabel = true,
showChange = false,
showPrevious = false,
className,
}: MetricCardProps) => {
const diff = value - change;
const pct = ((value - diff) / diff) * 100;
@ -36,26 +34,19 @@ export const MetricCard = ({
const prevProps = useSpring({ x: Number(diff) || 0, from: { x: 0 } });
return (
<div className={classNames(styles.card, className, showPrevious && styles.compare)}>
{showLabel && <div className={styles.label}>{label}</div>}
<AnimatedDiv className={styles.value} title={value?.toString()}>
{props?.x?.to(x => formatValue(x))}
</AnimatedDiv>
<Column className={styles.card} justifyContent="center">
{showLabel && <Text weight="bold">{label}</Text>}
<Text size="8" weight="bold" wrap="nowrap">
<AnimatedDiv title={value?.toString()}>{props?.x?.to(x => formatValue(x))}</AnimatedDiv>
</Text>
{showChange && (
<ChangeLabel
className={styles.change}
value={change}
title={formatValue(change)}
reverseColors={reverseColors}
>
<ChangeLabel value={change} title={formatValue(change)} reverseColors={reverseColors}>
<AnimatedDiv>{changeProps?.x?.to(x => `${Math.abs(~~x)}%`)}</AnimatedDiv>
</ChangeLabel>
)}
{showPrevious && (
<AnimatedDiv className={classNames(styles.value, styles.prev)} title={diff.toString()}>
{prevProps?.x?.to(x => formatValue(x))}
</AnimatedDiv>
<AnimatedDiv title={diff.toString()}>{prevProps?.x?.to(x => formatValue(x))}</AnimatedDiv>
)}
</div>
</Column>
);
};

View file

@ -1,13 +0,0 @@
.bar {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, max-content));
gap: 20px;
width: 100%;
position: relative;
}
@media screen and (max-width: 768px) {
.bar {
grid-template-columns: 1fr 1fr;
}
}

View file

@ -1,8 +1,6 @@
import { ReactNode } from 'react';
import { Loading, Row } from '@umami/react-zen';
import { cloneChildren } from '@/lib/react';
import { Grid, Loading } from '@umami/react-zen';
import { ErrorMessage } from '@/components/common/ErrorMessage';
import { formatLongNumber } from '@/lib/format';
export interface MetricsBarProps {
isLoading?: boolean;
@ -12,18 +10,15 @@ export interface MetricsBarProps {
}
export function MetricsBar({ children, isLoading, isFetched, error }: MetricsBarProps) {
const formatFunc = n => (n >= 0 ? formatLongNumber(n) : `-${formatLongNumber(Math.abs(n))}`);
return (
<Row>
<>
{isLoading && !isFetched && <Loading icon="dots" />}
{error && <ErrorMessage />}
{!isLoading &&
!error &&
isFetched &&
cloneChildren(children, child => {
return { format: child?.props['format'] || formatFunc };
})}
</Row>
{!isLoading && !error && isFetched && (
<Grid columns="repeat(auto-fill, minmax(200px, 1fr))" width="100%" gapY="3">
{children}
</Grid>
)}
</>
);
}