From a15c7cd596c231fb0c4f461ca6fd67d34b7bde6a Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 7 May 2025 04:10:27 -0700 Subject: [PATCH] Updated tables. Added MenuButton. --- package.json | 2 +- pnpm-lock.yaml | 18 +-- src/app/(main)/MenuBar.tsx | 20 ++- .../settings/profile/LanguageSetting.tsx | 2 +- .../settings/profile/PasswordChangeButton.tsx | 4 +- .../(main)/settings/profile/ThemeSetting.tsx | 10 +- src/app/(main)/settings/teams/TeamAddForm.tsx | 6 +- .../(main)/settings/teams/TeamJoinForm.tsx | 2 +- src/app/(main)/settings/teams/TeamsTable.tsx | 28 ++-- src/app/(main)/settings/users/UserAddForm.tsx | 6 +- .../(main)/settings/users/UserDeleteForm.tsx | 2 +- src/app/(main)/settings/users/UsersTable.tsx | 129 ++++++++++++------ .../settings/websites/WebsitesTable.tsx | 39 +++--- .../[websiteId]/WebsiteTransferForm.tsx | 1 - .../settings/members/TeamMemberEditButton.tsx | 17 ++- .../settings/members/TeamMembersTable.tsx | 23 ++-- .../settings/websites/TeamWebsitesTable.tsx | 35 +++-- .../[websiteId]/WebsiteCompareTables.tsx | 28 ++-- .../[websiteId]/WebsiteExpandedView.tsx | 47 ++++--- src/components/charts/BarChart.tsx | 33 ++--- src/components/common/SideMenu.tsx | 12 +- src/components/hooks/useNavigation.ts | 5 +- src/components/input/DateFilter.tsx | 6 +- src/components/input/MenuButton.tsx | 30 ++++ src/components/input/TeamsButton.tsx | 2 +- src/components/input/WebsiteSelect.tsx | 25 ++-- src/components/metrics/PageviewsChart.tsx | 9 +- 27 files changed, 334 insertions(+), 207 deletions(-) create mode 100644 src/components/input/MenuButton.tsx diff --git a/package.json b/package.json index 5e0b988d..dc4cbbb7 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "@prisma/extension-read-replicas": "^0.4.1", "@react-spring/web": "^9.7.3", "@tanstack/react-query": "^5.74.11", - "@umami/react-zen": "^0.90.0", + "@umami/react-zen": "^0.96.0", "@umami/redis-client": "^0.27.0", "bcryptjs": "^2.4.3", "chalk": "^4.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 78de93d3..003ac2a9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,8 +39,8 @@ importers: specifier: ^5.74.11 version: 5.74.11(react@19.1.0) '@umami/react-zen': - specifier: ^0.90.0 - version: 0.90.0(@babel/core@7.26.10)(@types/react@19.1.2)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0)) + specifier: ^0.96.0 + version: 0.96.0(@babel/core@7.26.10)(@types/react@19.1.2)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0)) '@umami/redis-client': specifier: ^0.27.0 version: 0.27.0 @@ -2992,8 +2992,8 @@ packages: resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} engines: {node: ^16.0.0 || >=18.0.0} - '@umami/react-zen@0.90.0': - resolution: {integrity: sha512-Hj0/GSQPUtiRwq1ri3nX+anWp5udNQmrKZcHOH/j1B3z4KL/AW+llYyXqP9loG1N+NgEzW66P791Pac8Wo7qpw==} + '@umami/react-zen@0.96.0': + resolution: {integrity: sha512-ojY3sOehvGbYN29fHrPHyBlsrrjOFJdlftlYTsPQSbX/25SiD1Sk6/WqcY/YEJZS71gZyklc4yjd/EKSZgxJqw==} '@umami/redis-client@0.27.0': resolution: {integrity: sha512-SbHTpxhgeZyTBUSp2zdZM+XUtpsaSL4Tad8QXIEhEtjWhvvfoornyT5kLuyYCVtzSAT4daALeGmOO1z6EE1KcA==} @@ -6350,8 +6350,8 @@ packages: peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 - react-hook-form@7.56.1: - resolution: {integrity: sha512-qWAVokhSpshhcEuQDSANHx3jiAEFzu2HAaaQIzi/r9FNPm1ioAvuJSD4EuZzWd7Al7nTRKcKPnBKO7sRn+zavQ==} + react-hook-form@7.56.2: + resolution: {integrity: sha512-vpfuHuQMF/L6GpuQ4c3ZDo+pRYxIi40gQqsCmmfUBwm+oqvBhKhwghCuj2o00YCgSfU6bR9KC/xnQGWm3Gr08A==} engines: {node: '>=18.0.0'} peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 @@ -10784,7 +10784,7 @@ snapshots: '@typescript-eslint/types': 6.21.0 eslint-visitor-keys: 3.4.3 - '@umami/react-zen@0.90.0(@babel/core@7.26.10)(@types/react@19.1.2)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0))': + '@umami/react-zen@0.96.0(@babel/core@7.26.10)(@types/react@19.1.2)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0))': dependencies: '@fontsource/jetbrains-mono': 5.2.5 '@internationalized/date': 3.8.0 @@ -10798,7 +10798,7 @@ snapshots: react: 19.1.0 react-aria-components: 1.8.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-dom: 19.1.0(react@19.1.0) - react-hook-form: 7.56.1(react@19.1.0) + react-hook-form: 7.56.2(react@19.1.0) react-icons: 5.5.0(react@19.1.0) thenby: 1.3.4 zustand: 5.0.4(@types/react@19.1.2)(immer@9.0.21)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)) @@ -14702,7 +14702,7 @@ snapshots: dependencies: react: 19.1.0 - react-hook-form@7.56.1(react@19.1.0): + react-hook-form@7.56.2(react@19.1.0): dependencies: react: 19.1.0 diff --git a/src/app/(main)/MenuBar.tsx b/src/app/(main)/MenuBar.tsx index f3fbe3f1..2d86f51e 100644 --- a/src/app/(main)/MenuBar.tsx +++ b/src/app/(main)/MenuBar.tsx @@ -6,9 +6,13 @@ import type { RowProps } from '@umami/react-zen/Row'; import useGlobalState from '@/components/hooks/useGlobalState'; import { Lucide } from '@/components/icons'; import { WebsiteSelect } from '@/components/input/WebsiteSelect'; +import { useNavigation } from '@/components/hooks'; export function MenuBar(props: RowProps) { const [isCollapsed, setCollapsed] = useGlobalState('sidenav-collapsed'); + const { websiteId } = useNavigation(); + + const handleSelect = () => {}; return ( - - - - - + + + {websiteId && ( + <> + + + + + + )} + diff --git a/src/app/(main)/settings/profile/LanguageSetting.tsx b/src/app/(main)/settings/profile/LanguageSetting.tsx index e71b88cb..19c5d74e 100644 --- a/src/app/(main)/settings/profile/LanguageSetting.tsx +++ b/src/app/(main)/settings/profile/LanguageSetting.tsx @@ -19,7 +19,7 @@ export function LanguageSetting() { const handleReset = () => saveLocale(DEFAULT_LOCALE); - const handleOpen = isOpen => { + const handleOpen = (isOpen: boolean) => { if (isOpen) { setSearch(''); } diff --git a/src/app/(main)/settings/profile/PasswordChangeButton.tsx b/src/app/(main)/settings/profile/PasswordChangeButton.tsx index 3fe41023..3429ca3f 100644 --- a/src/app/(main)/settings/profile/PasswordChangeButton.tsx +++ b/src/app/(main)/settings/profile/PasswordChangeButton.tsx @@ -1,6 +1,6 @@ import { Button, Icon, Text, useToast, DialogTrigger, Dialog, Modal } from '@umami/react-zen'; import { PasswordEditForm } from './PasswordEditForm'; -import { Icons } from '@/components/icons'; +import { Lucide } from '@/components/icons'; import { useMessages } from '@/components/hooks'; export function PasswordChangeButton() { @@ -15,7 +15,7 @@ export function PasswordChangeButton() { diff --git a/src/app/(main)/settings/profile/ThemeSetting.tsx b/src/app/(main)/settings/profile/ThemeSetting.tsx index 4f18cf0b..c5b6a365 100644 --- a/src/app/(main)/settings/profile/ThemeSetting.tsx +++ b/src/app/(main)/settings/profile/ThemeSetting.tsx @@ -1,5 +1,5 @@ import { Row, Button, Icon, useTheme } from '@umami/react-zen'; -import { Icons } from '@/components/icons'; +import { Lucide } from '@/components/icons'; export function ThemeSetting() { const { theme, setTheme } = useTheme(); @@ -10,13 +10,13 @@ export function ThemeSetting() { variant={theme === 'light' ? 'primary' : 'secondary'} onPress={() => setTheme('light')} > - - + + diff --git a/src/app/(main)/settings/teams/TeamAddForm.tsx b/src/app/(main)/settings/teams/TeamAddForm.tsx index fe562e7f..80fd0c62 100644 --- a/src/app/(main)/settings/teams/TeamAddForm.tsx +++ b/src/app/(main)/settings/teams/TeamAddForm.tsx @@ -30,12 +30,12 @@ export function TeamAddForm({ onSave, onClose }: { onSave: () => void; onClose: - - {formatMessage(labels.save)} - + + {formatMessage(labels.save)} + ); diff --git a/src/app/(main)/settings/teams/TeamJoinForm.tsx b/src/app/(main)/settings/teams/TeamJoinForm.tsx index 2016aff3..91ec5aaf 100644 --- a/src/app/(main)/settings/teams/TeamJoinForm.tsx +++ b/src/app/(main)/settings/teams/TeamJoinForm.tsx @@ -34,8 +34,8 @@ export function TeamJoinForm({ onSave, onClose }: { onSave: () => void; onClose: - {formatMessage(labels.join)} + {formatMessage(labels.join)} ); diff --git a/src/app/(main)/settings/teams/TeamsTable.tsx b/src/app/(main)/settings/teams/TeamsTable.tsx index 179f7b7f..d358e6f2 100644 --- a/src/app/(main)/settings/teams/TeamsTable.tsx +++ b/src/app/(main)/settings/teams/TeamsTable.tsx @@ -1,8 +1,8 @@ -import { DataColumn, DataTable, Icon, Text } from '@umami/react-zen'; +import { DataColumn, DataTable, Icon, MenuItem, Text, Row } from '@umami/react-zen'; import { useMessages } from '@/components/hooks'; import { Icons } from '@/components/icons'; import { ROLES } from '@/lib/constants'; -import { LinkButton } from '@/components/common/LinkButton'; +import { MenuButton } from '@/components/input/MenuButton'; export function TeamsTable({ data = [], @@ -32,12 +32,24 @@ export function TeamsTable({ const { id } = row; return ( - - - - - {formatMessage(labels.view)} - + + + + + + + {formatMessage(labels.view)} + + + + + + + + {formatMessage(labels.edit)} + + + ); }} diff --git a/src/app/(main)/settings/users/UserAddForm.tsx b/src/app/(main)/settings/users/UserAddForm.tsx index ed5cc0df..626728cf 100644 --- a/src/app/(main)/settings/users/UserAddForm.tsx +++ b/src/app/(main)/settings/users/UserAddForm.tsx @@ -62,12 +62,12 @@ export function UserAddForm({ onSave, onClose }) { - - {formatMessage(labels.save)} - + + {formatMessage(labels.save)} + ); diff --git a/src/app/(main)/settings/users/UserDeleteForm.tsx b/src/app/(main)/settings/users/UserDeleteForm.tsx index 37bea24c..18baf99b 100644 --- a/src/app/(main)/settings/users/UserDeleteForm.tsx +++ b/src/app/(main)/settings/users/UserDeleteForm.tsx @@ -23,7 +23,7 @@ export function UserDeleteForm({ userId, username, onSave, onClose }) { return ( {username}, + target:  {username}, })} onConfirm={handleConfirm} onClose={onClose} diff --git a/src/app/(main)/settings/users/UsersTable.tsx b/src/app/(main)/settings/users/UsersTable.tsx index f064d30c..6b9b6a97 100644 --- a/src/app/(main)/settings/users/UsersTable.tsx +++ b/src/app/(main)/settings/users/UsersTable.tsx @@ -1,9 +1,22 @@ -import { Row, Button, Text, Icon, Icons, DataTable, DataColumn } from '@umami/react-zen'; -import Link from 'next/link'; +import { + Row, + Text, + Icon, + Icons, + DataTable, + DataColumn, + MenuItem, + MenuSeparator, + Modal, + Dialog, +} from '@umami/react-zen'; import { formatDistance } from 'date-fns'; import { ROLES } from '@/lib/constants'; import { useMessages, useLocale } from '@/components/hooks'; -import { UserDeleteButton } from './UserDeleteButton'; +import { MenuButton } from '@/components/input/MenuButton'; +import Link from 'next/link'; +import { useState } from 'react'; +import { UserDeleteForm } from '@/app/(main)/settings/users/UserDeleteForm'; export function UsersTable({ data = [], @@ -14,48 +27,78 @@ export function UsersTable({ }) { const { formatMessage, labels } = useMessages(); const { dateLocale } = useLocale(); + const [deleteUser, setDeleteUser] = useState(null); + const handleDelete = () => {}; return ( - - - - {(row: any) => - formatMessage( - labels[Object.keys(ROLES).find(key => ROLES[key] === row.role)] || labels.unknown, - ) - } - - - {(row: any) => - formatDistance(new Date(row.createdAt), new Date(), { - addSuffix: true, - locale: dateLocale, - }) - } - - - {(row: any) => row._count.websiteUser} - - {showActions && ( - - {(row: any) => { - const { id, username } = row; - return ( - - - - - ); - }} + <> + + + {(row: any) => {row.username}} - )} - + + {(row: any) => + formatMessage( + labels[Object.keys(ROLES).find(key => ROLES[key] === row.role)] || labels.unknown, + ) + } + + + {(row: any) => + formatDistance(new Date(row.createdAt), new Date(), { + addSuffix: true, + locale: dateLocale, + }) + } + + + {(row: any) => row._count.websiteUser} + + {showActions && ( + + {(row: any) => { + const { id } = row; + + return ( + + + + + + + {formatMessage(labels.edit)} + + + + setDeleteUser(row)}> + + + + + {formatMessage(labels.delete)} + + + + ); + }} + + )} + + + + {({ close }) => ( + { + close(); + setDeleteUser(null); + }} + /> + )} + + + ); } diff --git a/src/app/(main)/settings/websites/WebsitesTable.tsx b/src/app/(main)/settings/websites/WebsitesTable.tsx index 087dd6aa..9328d0c5 100644 --- a/src/app/(main)/settings/websites/WebsitesTable.tsx +++ b/src/app/(main)/settings/websites/WebsitesTable.tsx @@ -1,7 +1,8 @@ import { ReactNode } from 'react'; -import { Row, Text, Icon, Icons, DataTable, DataColumn, Button } from '@umami/react-zen'; -import Link from 'next/link'; +import { Row, Text, Icon, DataTable, DataColumn, MenuItem } from '@umami/react-zen'; import { useMessages, useNavigation } from '@/components/hooks'; +import { MenuButton } from '@/components/input/MenuButton'; +import { Lucide } from '@/components/icons'; export interface WebsitesTableProps { data: any[]; @@ -36,28 +37,28 @@ export function WebsitesTable({ const websiteId = row.id; return ( - + {allowEdit && ( - - )} - {allowView && ( - + + )} - + {allowView && ( + + + + + + {formatMessage(labels.edit)} + + + )} + ); }} diff --git a/src/app/(main)/settings/websites/[websiteId]/WebsiteTransferForm.tsx b/src/app/(main)/settings/websites/[websiteId]/WebsiteTransferForm.tsx index 925bd193..610f3268 100644 --- a/src/app/(main)/settings/websites/[websiteId]/WebsiteTransferForm.tsx +++ b/src/app/(main)/settings/websites/[websiteId]/WebsiteTransferForm.tsx @@ -57,7 +57,6 @@ export function WebsiteTransferForm({ }; const handleChange = (key: Key) => { - console.log('KEY', key); setTeamId(key as string); }; diff --git a/src/app/(main)/teams/[teamId]/settings/members/TeamMemberEditButton.tsx b/src/app/(main)/teams/[teamId]/settings/members/TeamMemberEditButton.tsx index 4ddef649..a6844da6 100644 --- a/src/app/(main)/teams/[teamId]/settings/members/TeamMemberEditButton.tsx +++ b/src/app/(main)/teams/[teamId]/settings/members/TeamMemberEditButton.tsx @@ -1,6 +1,7 @@ import { useMessages, useModified } from '@/components/hooks'; import { - Button, + Row, + Pressable, Icon, Icons, Modal, @@ -34,12 +35,14 @@ export function TeamMemberEditButton({ return ( - + + + + + + {formatMessage(labels.edit)} + + {({ close }) => ( diff --git a/src/app/(main)/teams/[teamId]/settings/members/TeamMembersTable.tsx b/src/app/(main)/teams/[teamId]/settings/members/TeamMembersTable.tsx index 25f6982c..34f3ac3b 100644 --- a/src/app/(main)/teams/[teamId]/settings/members/TeamMembersTable.tsx +++ b/src/app/(main)/teams/[teamId]/settings/members/TeamMembersTable.tsx @@ -1,8 +1,9 @@ -import { DataColumn, DataTable } from '@umami/react-zen'; +import { DataColumn, DataTable, MenuItem } from '@umami/react-zen'; import { useMessages, useLoginQuery } from '@/components/hooks'; import { ROLES } from '@/lib/constants'; import { TeamMemberRemoveButton } from './TeamMemberRemoveButton'; import { TeamMemberEditButton } from './TeamMemberEditButton'; +import { MenuButton } from '@/components/input/MenuButton'; export function TeamMembersTable({ data = [], @@ -37,14 +38,18 @@ export function TeamMembersTable({ allowEdit && row?.role !== ROLES.teamOwner && user?.id !== row?.user?.id && ( - <> - - - + + + + + + + + ) ); }} diff --git a/src/app/(main)/teams/[teamId]/settings/websites/TeamWebsitesTable.tsx b/src/app/(main)/teams/[teamId]/settings/websites/TeamWebsitesTable.tsx index 798f6cbc..1d25ff7b 100644 --- a/src/app/(main)/teams/[teamId]/settings/websites/TeamWebsitesTable.tsx +++ b/src/app/(main)/teams/[teamId]/settings/websites/TeamWebsitesTable.tsx @@ -1,7 +1,7 @@ -import { DataColumn, DataTable, Icon, Text } from '@umami/react-zen'; +import { DataColumn, DataTable, Icon, MenuItem, Text, Row } from '@umami/react-zen'; import { useLoginQuery, useMessages } from '@/components/hooks'; import { Icons } from '@/components/icons'; -import { LinkButton } from '@/components/common/LinkButton'; +import { MenuButton } from '@/components/input/MenuButton'; export function TeamWebsitesTable({ teamId, @@ -25,23 +25,28 @@ export function TeamWebsitesTable({ {(row: any) => { const { id: websiteId } = row; + return ( - <> - {allowEdit && (teamId || user?.isAdmin) && ( - + + + - + - {formatMessage(labels.edit)} - + {formatMessage(labels.view)} + + + {allowEdit && (teamId || user?.isAdmin) && ( + + + + + + {formatMessage(labels.edit)} + + )} - - - - - {formatMessage(labels.view)} - - + ); }} diff --git a/src/app/(main)/websites/[websiteId]/WebsiteCompareTables.tsx b/src/app/(main)/websites/[websiteId]/WebsiteCompareTables.tsx index d78e4df5..9b633cb3 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteCompareTables.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteCompareTables.tsx @@ -53,72 +53,72 @@ export function WebsiteCompareTables({ websiteId }: { websiteId: string }) { const items = [ { - key: 'url', + id: 'url', label: formatMessage(labels.pages), url: renderUrl({ view: 'url' }), }, { - key: 'referrer', + id: 'referrer', label: formatMessage(labels.referrers), url: renderUrl({ view: 'referrer' }), }, { - key: 'browser', + id: 'browser', label: formatMessage(labels.browsers), url: renderUrl({ view: 'browser' }), }, { - key: 'os', + id: 'os', label: formatMessage(labels.os), url: renderUrl({ view: 'os' }), }, { - key: 'device', + id: 'device', label: formatMessage(labels.devices), url: renderUrl({ view: 'device' }), }, { - key: 'country', + id: 'country', label: formatMessage(labels.countries), url: renderUrl({ view: 'country' }), }, { - key: 'region', + id: 'region', label: formatMessage(labels.regions), url: renderUrl({ view: 'region' }), }, { - key: 'city', + id: 'city', label: formatMessage(labels.cities), url: renderUrl({ view: 'city' }), }, { - key: 'language', + id: 'language', label: formatMessage(labels.languages), url: renderUrl({ view: 'language' }), }, { - key: 'screen', + id: 'screen', label: formatMessage(labels.screens), url: renderUrl({ view: 'screen' }), }, { - key: 'event', + id: 'event', label: formatMessage(labels.events), url: renderUrl({ view: 'event' }), }, { - key: 'query', + id: 'query', label: formatMessage(labels.queryParameters), url: renderUrl({ view: 'query' }), }, { - key: 'host', + id: 'host', label: formatMessage(labels.hosts), url: renderUrl({ view: 'host' }), }, { - key: 'tag', + id: 'tag', label: formatMessage(labels.tags), url: renderUrl({ view: 'tag' }), }, diff --git a/src/app/(main)/websites/[websiteId]/WebsiteExpandedView.tsx b/src/app/(main)/websites/[websiteId]/WebsiteExpandedView.tsx index 92dda918..194154c0 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteExpandedView.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteExpandedView.tsx @@ -17,6 +17,7 @@ import { RegionsTable } from '@/components/metrics/RegionsTable'; import { ScreenTable } from '@/components/metrics/ScreenTable'; import { TagsTable } from '@/components/metrics/TagsTable'; import { ChannelsTable } from '@/components/metrics/ChannelsTable'; +import { Panel } from '@/components/common/Panel'; const views = { url: PagesTable, @@ -134,27 +135,29 @@ export function WebsiteExpandedView({ const DetailsComponent = views[view] || (() => null); return ( - - - - - - - {formatMessage(labels.back)} - - - - - - - + + + + + + + + {formatMessage(labels.back)} + + + + + + + + ); } diff --git a/src/components/charts/BarChart.tsx b/src/components/charts/BarChart.tsx index e5502ae7..f0cdc779 100644 --- a/src/components/charts/BarChart.tsx +++ b/src/components/charts/BarChart.tsx @@ -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 ( diff --git a/src/components/common/SideMenu.tsx b/src/components/common/SideMenu.tsx index af997b64..e33b31eb 100644 --- a/src/components/common/SideMenu.tsx +++ b/src/components/common/SideMenu.tsx @@ -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 ( - {items.map(({ id, label, url }) => { + {items.map(({ id, label, url, icon }) => { return ( - {label} + + {icon && {icon}} + {label} + ); })} diff --git a/src/components/hooks/useNavigation.ts b/src/components/hooks/useNavigation.ts index 194d7154..5cad6ba1 100644 --- a/src/components/hooks/useNavigation.ts +++ b/src/components/hooks/useNavigation.ts @@ -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 }; } diff --git a/src/components/input/DateFilter.tsx b/src/components/input/DateFilter.tsx index d316d677..a4ed510b 100644 --- a/src/components/input/DateFilter.tsx +++ b/src/components/input/DateFilter.tsx @@ -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') ? ( ) : ( defaultChildren @@ -103,10 +102,9 @@ export function DateFilter({ return ( <> {({ id, name }: any) => {name}} diff --git a/src/components/metrics/PageviewsChart.tsx b/src/components/metrics/PageviewsChart.tsx index 68007501..882eff74 100644 --- a/src/components/metrics/PageviewsChart.tsx +++ b/src/components/metrics/PageviewsChart.tsx @@ -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 ( ); }