mirror of
https://github.com/umami-software/umami.git
synced 2026-02-06 13:47:15 +01:00
Updated menus, chart tooltips, styles.
This commit is contained in:
parent
0a16ab38e4
commit
92b283486e
23 changed files with 179 additions and 208 deletions
|
|
@ -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} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
23
src/components/charts/ChartTooltip.tsx
Normal file
23
src/components/charts/ChartTooltip.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { Text, List, ListItem } from '@umami/react-zen';
|
||||
|
||||
export interface MenuNavProps {
|
||||
export interface SideMenuProps {
|
||||
items: { id: string; label: string; url: string }[];
|
||||
selectedKey?: string;
|
||||
}
|
||||
|
||||
export function SideMenu({ items, selectedKey }: MenuNavProps) {
|
||||
export function SideMenu({ items, selectedKey }: SideMenuProps) {
|
||||
return (
|
||||
<List>
|
||||
{items.map(({ id, label, url }) => {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export function DateFilter({
|
|||
value,
|
||||
onChange,
|
||||
showAllTime = false,
|
||||
...props
|
||||
}: DateFilterProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const [showPicker, setShowPicker] = useState(false);
|
||||
|
|
@ -102,6 +103,7 @@ export function DateFilter({
|
|||
return (
|
||||
<>
|
||||
<Select
|
||||
{...props}
|
||||
selectedKey={value}
|
||||
placeholder={formatMessage(labels.selectDate)}
|
||||
onSelectionChange={handleChange}
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
.legend {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: var(--font-size-sm);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.label + .label {
|
||||
margin-inline-start: 20px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
color: var(--base400);
|
||||
}
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
import { StatusLight } from '@umami/react-zen';
|
||||
import { Row, StatusLight, Text } from '@umami/react-zen';
|
||||
import { colord } from 'colord';
|
||||
import classNames from 'classnames';
|
||||
import { LegendItem } from 'chart.js/auto';
|
||||
import styles from './Legend.module.css';
|
||||
|
||||
export function Legend({
|
||||
items = [],
|
||||
|
|
@ -16,21 +14,21 @@ export function Legend({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={styles.legend}>
|
||||
<Row gap wrap="wrap" justifyContent="center">
|
||||
{items.map(item => {
|
||||
const { text, fillStyle, hidden } = item;
|
||||
const color = colord(fillStyle);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={text}
|
||||
className={classNames(styles.label, { [styles.hidden]: hidden })}
|
||||
onClick={() => onClick(item)}
|
||||
>
|
||||
<StatusLight color={color.alpha(color.alpha() + 0.2).toHex()}>{text}</StatusLight>
|
||||
</div>
|
||||
<Row key={text} onClick={() => onClick(item)}>
|
||||
<StatusLight color={color.alpha(color.alpha() + 0.2).toHex()}>
|
||||
<Text size="1" color={hidden ? 'disabled' : undefined} wrap="nowrap">
|
||||
{text}
|
||||
</Text>
|
||||
</StatusLight>
|
||||
</Row>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
16
src/components/svg/Security.tsx
Normal file
16
src/components/svg/Security.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import * as React from 'react';
|
||||
import type { SVGProps } from 'react';
|
||||
const SvgSecurity = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={512}
|
||||
height={512}
|
||||
data-name="Layer 1"
|
||||
viewBox="0 0 36 36"
|
||||
{...props}
|
||||
>
|
||||
<path d="M18 34a1.1 1.1 0 0 1-.48-.11l-4.87-2.43A13.79 13.79 0 0 1 5 19.05V6.91a1.07 1.07 0 0 1 1.05-1.07h3.47a7.45 7.45 0 0 0 4-1.19l3.87-2.48a1.07 1.07 0 0 1 1.15 0l3.87 2.48a7.45 7.45 0 0 0 4 1.19h3.47A1.07 1.07 0 0 1 31 6.91v12.14a13.79 13.79 0 0 1-7.67 12.4l-4.87 2.43A1.1 1.1 0 0 1 18 34M7.12 8v11.05a11.67 11.67 0 0 0 6.49 10.49l4.39 2.2 4.39-2.2a11.67 11.67 0 0 0 6.49-10.49V8h-2.4a9.57 9.57 0 0 1-5.19-1.53L18 4.33l-3.29 2.12A9.57 9.57 0 0 1 9.52 8z" />
|
||||
<path d="M18 18.8a4.8 4.8 0 1 1 4.8-4.8 4.81 4.81 0 0 1-4.8 4.8m0-7.47A2.67 2.67 0 1 0 20.67 14 2.67 2.67 0 0 0 18 11.34zM24.4 24.67h-2.13a2.14 2.14 0 0 0-2.13-2.13h-4.28a2.13 2.13 0 0 0-2.13 2.13H11.6a4.26 4.26 0 0 1 4.26-4.26h4.27a4.27 4.27 0 0 1 4.27 4.26" />
|
||||
</svg>
|
||||
);
|
||||
export default SvgSecurity;
|
||||
Loading…
Add table
Add a link
Reference in a new issue