Updated menus, chart tooltips, styles.

This commit is contained in:
Mike Cao 2025-05-05 01:36:16 -07:00
parent 0a16ab38e4
commit 92b283486e
23 changed files with 179 additions and 208 deletions

View file

@ -1,9 +1,24 @@
import { useMemo, useState } from 'react';
import { useTheme } from '@umami/react-zen';
import { BarChartTooltip } from '@/components/charts/BarChartTooltip';
import { ChartTooltip } from '@/components/charts/ChartTooltip';
import { Chart, ChartProps } from '@/components/charts/Chart';
import { useLocale } from '@/components/hooks';
import { renderNumberLabels } from '@/lib/charts';
import { getThemeColors } from '@/lib/colors';
import { formatDate } from '@/lib/date';
import { formatLongCurrency, formatLongNumber } from '@/lib/format';
const dateFormats = {
millisecond: 'T',
second: 'pp',
minute: 'p',
hour: 'p - PP',
day: 'PPPP',
week: 'PPPP',
month: 'LLLL yyyy',
quarter: 'qqq',
year: 'yyyy',
};
export interface BarChartProps extends ChartProps {
unit: string;
@ -18,22 +33,23 @@ export interface BarChartProps extends ChartProps {
isAllTime?: boolean;
}
export function BarChart(props: BarChartProps) {
export function BarChart({
renderXLabel,
renderYLabel,
unit,
XAxisType = 'time',
YAxisType = 'linear',
stacked = false,
minDate,
maxDate,
currency,
isAllTime,
...props
}: BarChartProps) {
const [tooltip, setTooltip] = useState(null);
const { theme } = useTheme();
const { locale } = useLocale();
const { colors } = getThemeColors(theme);
const {
renderXLabel,
renderYLabel,
unit,
XAxisType = 'time',
YAxisType = 'linear',
stacked = false,
minDate,
maxDate,
currency,
isAllTime,
} = props;
const options: any = useMemo(() => {
return {
@ -80,9 +96,23 @@ export function BarChart(props: BarChartProps) {
}, [colors, unit, stacked, renderXLabel, renderYLabel]);
const handleTooltip = ({ tooltip }: { tooltip: any }) => {
const { opacity } = tooltip;
const { opacity, labelColors, dataPoints } = tooltip;
setTooltip(opacity ? tooltip : null);
if (opacity) {
setTooltip({
title: formatDate(
new Date(dataPoints[0].raw?.d || dataPoints[0].raw?.x || dataPoints[0].raw),
dateFormats[unit],
locale,
),
color: labelColors?.[0]?.backgroundColor,
value: currency
? formatLongCurrency(dataPoints[0].raw.y, currency)
: `${formatLongNumber(dataPoints[0].raw.y)} ${dataPoints[0].dataset.label}`,
});
} else {
setTooltip(null);
}
};
return (
@ -92,9 +122,9 @@ export function BarChart(props: BarChartProps) {
type="bar"
chartOptions={options}
onTooltip={handleTooltip}
style={{ height: 400 }}
height="400px"
/>
{tooltip && <BarChartTooltip tooltip={tooltip} unit={unit} currency={currency} />}
{tooltip && <ChartTooltip {...tooltip} />}
</>
);
}

View file

@ -1,38 +0,0 @@
import { useLocale } from '@/components/hooks';
import { formatDate } from '@/lib/date';
import { formatLongCurrency, formatLongNumber } from '@/lib/format';
import { Column, Row, StatusLight, FloatingTooltip } from '@umami/react-zen';
const formats = {
millisecond: 'T',
second: 'pp',
minute: 'p',
hour: 'p - PP',
day: 'PPPP',
week: 'PPPP',
month: 'LLLL yyyy',
quarter: 'qqq',
year: 'yyyy',
};
export function BarChartTooltip({ tooltip, unit, currency }) {
const { locale } = useLocale();
const { labelColors, dataPoints } = tooltip;
return (
<FloatingTooltip>
<Column gap="3" fontSize="1">
<Row alignItems="center">
{formatDate(new Date(dataPoints[0].raw.d || dataPoints[0].raw.x), formats[unit], locale)}
</Row>
<Row alignItems="center">
<StatusLight color={labelColors?.[0]?.backgroundColor}>
{currency
? formatLongCurrency(dataPoints[0].raw.y, currency)
: `${formatLongNumber(dataPoints[0].raw.y)} ${dataPoints[0].dataset.label}`}
</StatusLight>
</Row>
</Column>
</FloatingTooltip>
);
}

View file

@ -1,27 +1,31 @@
import { Chart, ChartProps } from '@/components/charts/Chart';
import { useState } from 'react';
import { StatusLight } from '@umami/react-zen';
import { formatLongNumber } from '@/lib/format';
import { ChartTooltip } from '@/components/charts/ChartTooltip';
export interface BubbleChartProps extends ChartProps {
type?: 'bubble';
}
export function BubbleChart(props: BubbleChartProps) {
export function BubbleChart({ type = 'bubble', ...props }: BubbleChartProps) {
const [tooltip, setTooltip] = useState(null);
const { type = 'bubble' } = props;
const handleTooltip = ({ tooltip }) => {
const { labelColors, dataPoints } = tooltip;
const { opacity, labelColors, title, dataPoints } = tooltip;
setTooltip(
tooltip.opacity ? (
<StatusLight color={labelColors?.[0]?.backgroundColor}>
{formatLongNumber(dataPoints?.[0]?.raw)} {dataPoints?.[0]?.label}
</StatusLight>
) : null,
opacity
? {
color: labelColors?.[0]?.backgroundColor,
value: `${title}: ${dataPoints[0].raw}`,
}
: null,
);
};
return <Chart {...props} type={type} tooltip={tooltip} onTooltip={handleTooltip} />;
return (
<>
<Chart {...props} type={type} onTooltip={handleTooltip} />
{tooltip && <ChartTooltip {...tooltip} />}
</>
);
}

View file

@ -1,10 +1,11 @@
import { useState, useRef, useEffect, useMemo, HTMLAttributes } from 'react';
import { Loading } from '@umami/react-zen';
import { useState, useRef, useEffect, useMemo } from 'react';
import { Loading, Box, Column } from '@umami/react-zen';
import ChartJS, { LegendItem, ChartOptions } from 'chart.js/auto';
import { Legend } from '@/components/metrics/Legend';
import { DEFAULT_ANIMATION_DURATION } from '@/lib/constants';
import type { BoxProps } from '@umami/react-zen/Box';
export interface ChartProps extends HTMLAttributes<HTMLDivElement> {
export interface ChartProps extends BoxProps {
type?: 'bar' | 'bubble' | 'doughnut' | 'pie' | 'line' | 'polarArea' | 'radar' | 'scatter';
data?: object;
isLoading?: boolean;
@ -138,12 +139,12 @@ export function Chart({
}, [data, options]);
return (
<>
<div {...props}>
<Column gap="6">
<Box {...props}>
{isLoading && <Loading position="page" icon="dots" />}
<canvas ref={canvas} />
</div>
</Box>
<Legend items={legendItems} onClick={handleLegendClick} />
</>
</Column>
);
}

View file

@ -0,0 +1,23 @@
import { ReactNode } from 'react';
import { Column, Row, StatusLight, FloatingTooltip } from '@umami/react-zen';
export function ChartTooltip({
title,
color,
value,
}: {
title?: string;
color?: string;
value?: ReactNode;
}) {
return (
<FloatingTooltip>
<Column gap="3" fontSize="1">
{title && <Row alignItems="center">{title}</Row>}
<Row alignItems="center">
<StatusLight color={color}>{value}</StatusLight>
</Row>
</Column>
</FloatingTooltip>
);
}

View file

@ -1,27 +1,31 @@
import { Chart, ChartProps } from '@/components/charts/Chart';
import { useState } from 'react';
import { StatusLight } from '@umami/react-zen';
import { formatLongNumber } from '@/lib/format';
import { ChartTooltip } from '@/components/charts/ChartTooltip';
export interface PieChartProps extends ChartProps {
type?: 'doughnut' | 'pie';
}
export function PieChart(props: PieChartProps) {
export function PieChart({ type = 'pie', ...props }: PieChartProps) {
const [tooltip, setTooltip] = useState(null);
const { type = 'pie' } = props;
const handleTooltip = ({ tooltip }) => {
const { labelColors, dataPoints } = tooltip;
const { opacity, labelColors, title, dataPoints } = tooltip;
setTooltip(
tooltip.opacity ? (
<StatusLight color={labelColors?.[0]?.backgroundColor}>
{formatLongNumber(dataPoints?.[0]?.raw)} {dataPoints?.[0]?.label}
</StatusLight>
) : null,
opacity
? {
color: labelColors?.[0]?.backgroundColor,
value: `${title}: ${dataPoints[0].raw}`,
}
: null,
);
};
return <Chart {...props} type={type} tooltip={tooltip} onTooltip={handleTooltip} />;
return (
<>
<Chart {...props} type={type} onTooltip={handleTooltip} />
{tooltip && <ChartTooltip {...tooltip} />}
</>
);
}