Updated tables. Added MenuButton.

This commit is contained in:
Mike Cao 2025-05-07 04:10:27 -07:00
parent 92b283486e
commit a15c7cd596
27 changed files with 334 additions and 207 deletions

View file

@ -49,10 +49,11 @@ export function BarChart({
const [tooltip, setTooltip] = useState(null);
const { theme } = useTheme();
const { locale } = useLocale();
const { colors } = getThemeColors(theme);
const { colors } = useMemo(() => getThemeColors(theme), [theme]);
const options: any = useMemo(() => {
return {
__id: new Date().getTime(),
scales: {
x: {
type: XAxisType,
@ -98,21 +99,21 @@ export function BarChart({
const handleTooltip = ({ tooltip }: { tooltip: any }) => {
const { opacity, labelColors, dataPoints } = tooltip;
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);
}
setTooltip(
opacity
? {
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}`,
}
: null,
);
};
return (

View file

@ -1,17 +1,21 @@
import { Text, List, ListItem } from '@umami/react-zen';
import { ReactNode } from 'react';
import { Text, List, ListItem, Icon, Row } from '@umami/react-zen';
export interface SideMenuProps {
items: { id: string; label: string; url: string }[];
items: { id: string; label: string; url: string; icon?: ReactNode }[];
selectedKey?: string;
}
export function SideMenu({ items, selectedKey }: SideMenuProps) {
return (
<List>
{items.map(({ id, label, url }) => {
{items.map(({ id, label, url, icon }) => {
return (
<ListItem key={id} id={id} href={url}>
<Text weight={id === selectedKey ? 'bold' : 'regular'}>{label}</Text>
<Row alignItems="center" gap>
{icon && <Icon>{icon}</Icon>}
<Text weight={id === selectedKey ? 'bold' : 'regular'}>{label}</Text>
</Row>
</ListItem>
);
})}

View file

@ -6,7 +6,8 @@ export function useNavigation() {
const router = useRouter();
const pathname = usePathname();
const params = useSearchParams();
const [, teamId] = pathname.match(/^\/teams\/([a-f0-9-]+)/) || [];
const [, teamId] = pathname.match(/\/teams\/([a-f0-9-]+)/) || [];
const [, websiteId] = pathname.match(/\/websites\/([a-f0-9-]+)/) || [];
const query = useMemo<{ [key: string]: any }>(() => {
const obj = {};
@ -26,5 +27,5 @@ export function useNavigation() {
return teamId ? `/teams/${teamId}${url}` : url;
}
return { pathname, query, router, renderUrl, renderTeamUrl, teamId };
return { pathname, query, router, renderUrl, renderTeamUrl, teamId, websiteId };
}

View file

@ -22,7 +22,6 @@ export function DateFilter({
value,
onChange,
showAllTime = false,
...props
}: DateFilterProps) {
const { formatMessage, labels } = useMessages();
const [showPicker, setShowPicker] = useState(false);
@ -93,7 +92,7 @@ export function DateFilter({
};
const renderValue = ({ defaultChildren }) => {
return value.startsWith('range') ? (
return value?.startsWith('range') ? (
<DateDisplay startDate={startDate} endDate={endDate} />
) : (
defaultChildren
@ -103,10 +102,9 @@ export function DateFilter({
return (
<>
<Select
{...props}
selectedKey={value}
placeholder={formatMessage(labels.selectDate)}
onSelectionChange={handleChange}
onChange={handleChange}
renderValue={renderValue}
>
{options.map(({ label, value, divider }: any) => {

View file

@ -0,0 +1,30 @@
import { ReactNode, Key } from 'react';
import { DialogTrigger, Button, Menu, Popover, Icon } from '@umami/react-zen';
import { Lucide } from '@/components/icons';
export function MenuButton({
children,
onAction,
}: {
children: ReactNode;
onAction?: (action: string) => void;
}) {
const handleAction = (key: Key) => {
onAction?.(key.toString());
};
return (
<DialogTrigger>
<Button variant="outline">
<Icon>
<Lucide.Ellipsis />
</Icon>
</Button>
<Popover>
<Menu onAction={handleAction} style={{ minWidth: '140px' }}>
{children}
</Menu>
</Popover>
</DialogTrigger>
);
}

View file

@ -45,7 +45,7 @@ export function TeamsButton({
<Button className={className} variant="quiet">
<Row alignItems="center" gap="3">
<Icon>{teamId ? <Users /> : <User />}</Icon>
{showText && <Text weight="bold">{teamId ? team?.name : user.username}</Text>}
{showText && <Text>{teamId ? team?.name : user.username}</Text>}
<Icon rotate={90} size="xs">
<Icons.Chevron />
</Icon>

View file

@ -1,19 +1,23 @@
import { useState, Key } from 'react';
import { useState } from 'react';
import { Select, ListItem } from '@umami/react-zen';
import { useWebsites, useMessages } from '@/components/hooks';
import type { SelectProps } from '@umami/react-zen/Select';
export function WebsiteSelect({
websiteId,
teamId,
variant,
onSelect,
...props
}: {
websiteId?: string;
teamId?: string;
variant?: 'primary' | 'secondary' | 'outline' | 'quiet' | 'danger' | 'zero';
onSelect?: (key: any) => void;
}) {
} & SelectProps) {
const { formatMessage, labels } = useMessages();
const [search, setSearch] = useState('');
const [selectedId, setSelectedId] = useState<Key>(websiteId);
const [selectedId, setSelectedId] = useState(websiteId);
const queryResult = useWebsites({ teamId }, { search, pageSize: 5 });
@ -26,15 +30,20 @@ export function WebsiteSelect({
setSearch(value);
};
const items = queryResult?.result?.data as any[];
return (
<Select
items={queryResult?.result?.data as any[]}
value={selectedId as string}
onChange={handleSelect}
{...props}
items={items}
value={selectedId}
placeholder={formatMessage(labels.selectWebsite)}
allowSearch={true}
onSearch={handleSearch}
isLoading={queryResult.query.isLoading}
buttonProps={{ variant }}
allowSearch={true}
searchValue={search}
onSearch={handleSearch}
onChange={handleSelect}
>
{({ id, name }: any) => <ListItem key={id}>{name}</ListItem>}
</Select>

View file

@ -1,4 +1,4 @@
import { useMemo } from 'react';
import { useCallback, useMemo } from 'react';
import { useTheme } from '@umami/react-zen';
import { BarChart, BarChartProps } from '@/components/charts/BarChart';
import { useLocale, useMessages } from '@/components/hooks';
@ -28,8 +28,8 @@ export function PageviewsChart({
}: PageviewsChartProps) {
const { formatMessage, labels } = useMessages();
const { theme } = useTheme();
const { colors } = getThemeColors(theme);
const { locale } = useLocale();
const { colors } = useMemo(() => getThemeColors(theme), [theme]);
const chartData = useMemo(() => {
if (!data) {
@ -37,6 +37,7 @@ export function PageviewsChart({
}
return {
__id: new Date().getTime(),
datasets: [
{
label: formatMessage(labels.visitors),
@ -78,6 +79,8 @@ export function PageviewsChart({
};
}, [data, locale]);
const renderXLabel = useCallback(renderDateLabels(unit, locale), [unit, locale]);
return (
<BarChart
{...props}
@ -85,7 +88,7 @@ export function PageviewsChart({
unit={unit}
isLoading={isLoading}
isAllTime={isAllTime}
renderXLabel={renderDateLabels(unit, locale)}
renderXLabel={renderXLabel}
/>
);
}