mirror of
https://github.com/umami-software/umami.git
synced 2026-02-11 08:07:12 +01:00
Website header updates.
This commit is contained in:
parent
2b99274895
commit
96c2c32d14
26 changed files with 137 additions and 247 deletions
|
|
@ -78,7 +78,7 @@
|
||||||
"@react-spring/web": "^9.7.5",
|
"@react-spring/web": "^9.7.5",
|
||||||
"@tanstack/react-query": "^5.68.0",
|
"@tanstack/react-query": "^5.68.0",
|
||||||
"@umami/prisma-client": "^0.16.0",
|
"@umami/prisma-client": "^0.16.0",
|
||||||
"@umami/react-zen": "^0.76.0",
|
"@umami/react-zen": "^0.77.0",
|
||||||
"@umami/redis-client": "^0.27.0",
|
"@umami/redis-client": "^0.27.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
|
|
|
||||||
30
pnpm-lock.yaml
generated
30
pnpm-lock.yaml
generated
|
|
@ -45,8 +45,8 @@ importers:
|
||||||
specifier: ^0.16.0
|
specifier: ^0.16.0
|
||||||
version: 0.16.0(@prisma/client@6.5.0(prisma@6.5.0(typescript@5.8.2))(typescript@5.8.2))(@prisma/extension-read-replicas@0.4.0(@prisma/client@6.5.0(prisma@6.5.0(typescript@5.8.2))(typescript@5.8.2)))
|
version: 0.16.0(@prisma/client@6.5.0(prisma@6.5.0(typescript@5.8.2))(typescript@5.8.2))(@prisma/extension-read-replicas@0.4.0(@prisma/client@6.5.0(prisma@6.5.0(typescript@5.8.2))(typescript@5.8.2)))
|
||||||
'@umami/react-zen':
|
'@umami/react-zen':
|
||||||
specifier: ^0.76.0
|
specifier: ^0.77.0
|
||||||
version: 0.76.0(@babel/core@7.26.9)(@types/react@19.0.10)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.0.0))
|
version: 0.77.0(@babel/core@7.26.9)(@types/react@19.0.10)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.0.0))
|
||||||
'@umami/redis-client':
|
'@umami/redis-client':
|
||||||
specifier: ^0.27.0
|
specifier: ^0.27.0
|
||||||
version: 0.27.0
|
version: 0.27.0
|
||||||
|
|
@ -1314,8 +1314,8 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@dicebear/core': ^9.0.0
|
'@dicebear/core': ^9.0.0
|
||||||
|
|
||||||
'@emnapi/runtime@1.3.1':
|
'@emnapi/runtime@1.4.0':
|
||||||
resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==}
|
resolution: {integrity: sha512-64WYIf4UYcdLnbKn/umDlNjQDSS8AgZrI/R9+x5ilkUVFxXcA1Ebl+gQLc/6mERA4407Xof0R7wEyEuj091CVw==}
|
||||||
|
|
||||||
'@esbuild/aix-ppc64@0.25.1':
|
'@esbuild/aix-ppc64@0.25.1':
|
||||||
resolution: {integrity: sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==}
|
resolution: {integrity: sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==}
|
||||||
|
|
@ -2961,8 +2961,8 @@ packages:
|
||||||
'@prisma/client': ^4.8.0
|
'@prisma/client': ^4.8.0
|
||||||
'@prisma/extension-read-replicas': ^0.3.0
|
'@prisma/extension-read-replicas': ^0.3.0
|
||||||
|
|
||||||
'@umami/react-zen@0.76.0':
|
'@umami/react-zen@0.77.0':
|
||||||
resolution: {integrity: sha512-AjkRpJGPV514fo516csceh9kwQexfgZdZPli0bTui5FH9RObptgEev/ySW+x9dHWXJWch7GZ+81c+eNloX19YQ==}
|
resolution: {integrity: sha512-tdoPdCMfPOhBMEiGXZ62zfTG8qjto7Ii+mWXNX2qdPjZ+SP5AB8wnQpqIGiXoUZUnP41iWw0GS97sTZq3xeEBQ==}
|
||||||
|
|
||||||
'@umami/redis-client@0.27.0':
|
'@umami/redis-client@0.27.0':
|
||||||
resolution: {integrity: sha512-SbHTpxhgeZyTBUSp2zdZM+XUtpsaSL4Tad8QXIEhEtjWhvvfoornyT5kLuyYCVtzSAT4daALeGmOO1z6EE1KcA==}
|
resolution: {integrity: sha512-SbHTpxhgeZyTBUSp2zdZM+XUtpsaSL4Tad8QXIEhEtjWhvvfoornyT5kLuyYCVtzSAT4daALeGmOO1z6EE1KcA==}
|
||||||
|
|
@ -3329,8 +3329,8 @@ packages:
|
||||||
caniuse-lite@1.0.30001702:
|
caniuse-lite@1.0.30001702:
|
||||||
resolution: {integrity: sha512-LoPe/D7zioC0REI5W73PeR1e1MLCipRGq/VkovJnd6Df+QVqT+vT33OXCp8QUd7kA7RZrHWxb1B36OQKI/0gOA==}
|
resolution: {integrity: sha512-LoPe/D7zioC0REI5W73PeR1e1MLCipRGq/VkovJnd6Df+QVqT+vT33OXCp8QUd7kA7RZrHWxb1B36OQKI/0gOA==}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001707:
|
caniuse-lite@1.0.30001709:
|
||||||
resolution: {integrity: sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==}
|
resolution: {integrity: sha512-NgL3vUTnDrPCZ3zTahp4fsugQ4dc7EKTSzwQDPEel6DMoMnfH2jhry9n2Zm8onbSR+f/QtKHFOA+iAQu4kbtWA==}
|
||||||
|
|
||||||
caseless@0.12.0:
|
caseless@0.12.0:
|
||||||
resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==}
|
resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==}
|
||||||
|
|
@ -8404,7 +8404,7 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@dicebear/core': 9.2.2
|
'@dicebear/core': 9.2.2
|
||||||
|
|
||||||
'@emnapi/runtime@1.3.1':
|
'@emnapi/runtime@1.4.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
optional: true
|
optional: true
|
||||||
|
|
@ -8740,7 +8740,7 @@ snapshots:
|
||||||
|
|
||||||
'@img/sharp-wasm32@0.33.5':
|
'@img/sharp-wasm32@0.33.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/runtime': 1.3.1
|
'@emnapi/runtime': 1.4.0
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-win32-ia32@0.33.5':
|
'@img/sharp-win32-ia32@0.33.5':
|
||||||
|
|
@ -10646,7 +10646,7 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@umami/react-zen@0.76.0(@babel/core@7.26.9)(@types/react@19.0.10)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.0.0))':
|
'@umami/react-zen@0.77.0(@babel/core@7.26.9)(@types/react@19.0.10)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.0.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@fontsource/jetbrains-mono': 5.2.5
|
'@fontsource/jetbrains-mono': 5.2.5
|
||||||
'@react-aria/focus': 3.20.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
'@react-aria/focus': 3.20.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
|
@ -11120,13 +11120,13 @@ snapshots:
|
||||||
caniuse-api@3.0.0:
|
caniuse-api@3.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
browserslist: 4.24.4
|
browserslist: 4.24.4
|
||||||
caniuse-lite: 1.0.30001707
|
caniuse-lite: 1.0.30001709
|
||||||
lodash.memoize: 4.1.2
|
lodash.memoize: 4.1.2
|
||||||
lodash.uniq: 4.5.0
|
lodash.uniq: 4.5.0
|
||||||
|
|
||||||
caniuse-lite@1.0.30001702: {}
|
caniuse-lite@1.0.30001702: {}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001707: {}
|
caniuse-lite@1.0.30001709: {}
|
||||||
|
|
||||||
caseless@0.12.0: {}
|
caseless@0.12.0: {}
|
||||||
|
|
||||||
|
|
@ -13667,7 +13667,7 @@ snapshots:
|
||||||
'@swc/counter': 0.1.3
|
'@swc/counter': 0.1.3
|
||||||
'@swc/helpers': 0.5.15
|
'@swc/helpers': 0.5.15
|
||||||
busboy: 1.6.0
|
busboy: 1.6.0
|
||||||
caniuse-lite: 1.0.30001707
|
caniuse-lite: 1.0.30001709
|
||||||
postcss: 8.4.31
|
postcss: 8.4.31
|
||||||
react: 19.0.0
|
react: 19.0.0
|
||||||
react-dom: 19.0.0(react@19.0.0)
|
react-dom: 19.0.0(react@19.0.0)
|
||||||
|
|
@ -13692,7 +13692,7 @@ snapshots:
|
||||||
'@swc/counter': 0.1.3
|
'@swc/counter': 0.1.3
|
||||||
'@swc/helpers': 0.5.15
|
'@swc/helpers': 0.5.15
|
||||||
busboy: 1.6.0
|
busboy: 1.6.0
|
||||||
caniuse-lite: 1.0.30001707
|
caniuse-lite: 1.0.30001709
|
||||||
postcss: 8.4.31
|
postcss: 8.4.31
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
react-dom: 19.1.0(react@19.1.0)
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ export function WebsiteTransferForm({
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = (key: Key) => {
|
const handleChange = (key: Key) => {
|
||||||
|
console.log('KEY', key);
|
||||||
setTeamId(key as string);
|
setTeamId(key as string);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -73,10 +74,10 @@ export function WebsiteTransferForm({
|
||||||
</Text>
|
</Text>
|
||||||
<FormField name="teamId">
|
<FormField name="teamId">
|
||||||
{!isTeamWebsite && (
|
{!isTeamWebsite && (
|
||||||
<Select onSelectionChange={handleChange} value={teamId}>
|
<Select onSelectionChange={handleChange} selectedKey={teamId}>
|
||||||
{items.map(({ id, name }) => {
|
{items.map(({ id, name }) => {
|
||||||
return (
|
return (
|
||||||
<ListItem key={id} id={id}>
|
<ListItem key={`${id}!!!!`} id={`${id}????`}>
|
||||||
{name}
|
{name}
|
||||||
</ListItem>
|
</ListItem>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { Row } from '@umami/react-zen';
|
||||||
|
|
||||||
|
export function WebsiteCompareBar({ websiteId }: { websiteId: string }) {
|
||||||
|
return <Row>compare {websiteId}</Row>;
|
||||||
|
}
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
.button {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { Button, Icon, Icons, MenuTrigger, Popover, Text } from '@umami/react-ze
|
||||||
import { FilterSelectForm } from '@/app/(main)/reports/[reportId]/FilterSelectForm';
|
import { FilterSelectForm } from '@/app/(main)/reports/[reportId]/FilterSelectForm';
|
||||||
import { useFields, useMessages, useNavigation, useDateRange } from '@/components/hooks';
|
import { useFields, useMessages, useNavigation, useDateRange } from '@/components/hooks';
|
||||||
import { OPERATOR_PREFIXES } from '@/lib/constants';
|
import { OPERATOR_PREFIXES } from '@/lib/constants';
|
||||||
import styles from './WebsiteFilterButton.module.css';
|
|
||||||
|
|
||||||
export function WebsiteFilterButton({
|
export function WebsiteFilterButton({
|
||||||
websiteId,
|
websiteId,
|
||||||
|
|
@ -28,7 +27,7 @@ export function WebsiteFilterButton({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuTrigger>
|
<MenuTrigger>
|
||||||
<Button className={styles.button} variant="quiet">
|
<Button variant="quiet">
|
||||||
<Icon>
|
<Icon>
|
||||||
<Icons.Plus />
|
<Icons.Plus />
|
||||||
</Icon>
|
</Icon>
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,4 @@
|
||||||
import {
|
import { Column, Row, Heading } from '@umami/react-zen';
|
||||||
Column,
|
|
||||||
Row,
|
|
||||||
Heading,
|
|
||||||
MenuTrigger,
|
|
||||||
Button,
|
|
||||||
Icon,
|
|
||||||
Icons,
|
|
||||||
Popover,
|
|
||||||
Menu,
|
|
||||||
MenuItem,
|
|
||||||
MenuSeparator,
|
|
||||||
} from '@umami/react-zen';
|
|
||||||
import { Favicon } from '@/components/common/Favicon';
|
import { Favicon } from '@/components/common/Favicon';
|
||||||
import { ActiveUsers } from '@/components/metrics/ActiveUsers';
|
import { ActiveUsers } from '@/components/metrics/ActiveUsers';
|
||||||
import { WebsiteTabs } from '@/app/(main)/websites/[websiteId]/WebsiteTabs';
|
import { WebsiteTabs } from '@/app/(main)/websites/[websiteId]/WebsiteTabs';
|
||||||
|
|
@ -18,61 +6,35 @@ import { useWebsite } from '@/components/hooks/useWebsite';
|
||||||
import { WebsiteFilterButton } from '@/app/(main)/websites/[websiteId]/WebsiteFilterButton';
|
import { WebsiteFilterButton } from '@/app/(main)/websites/[websiteId]/WebsiteFilterButton';
|
||||||
import { WebsiteDateFilter } from '@/components/input/WebsiteDateFilter';
|
import { WebsiteDateFilter } from '@/components/input/WebsiteDateFilter';
|
||||||
import { FilterBar } from '@/components/metrics/FilterBar';
|
import { FilterBar } from '@/components/metrics/FilterBar';
|
||||||
import { useMessages } from '@/components/hooks';
|
import { WebsiteMenu } from '@/app/(main)/websites/[websiteId]/WebsiteMenu';
|
||||||
|
import { WebsiteCompareBar } from '@/app/(main)/websites/[websiteId]/WebsiteCompareBar';
|
||||||
|
|
||||||
export function WebsiteHeader({
|
export function WebsiteHeader({
|
||||||
websiteId,
|
|
||||||
showFilter = true,
|
showFilter = true,
|
||||||
allowEdit = true,
|
allowEdit = true,
|
||||||
compareMode = false,
|
|
||||||
}: {
|
}: {
|
||||||
websiteId: string;
|
|
||||||
showFilter?: boolean;
|
showFilter?: boolean;
|
||||||
allowEdit?: boolean;
|
allowEdit?: boolean;
|
||||||
compareMode?: boolean;
|
compareMode?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const website = useWebsite();
|
const website = useWebsite();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { id: websiteId, name, domain } = website || {};
|
||||||
const { name, domain } = website || {};
|
|
||||||
|
|
||||||
const items = [
|
|
||||||
{ label: formatMessage(labels.previousPeriod), value: 'prev' },
|
|
||||||
{ label: formatMessage(labels.previousYear), value: 'yoy' },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column marginY="6" gap="6">
|
<Column marginY="6" gap="6">
|
||||||
<Row alignItems="center" justifyContent="space-between" gap="3">
|
<Row alignItems="center" justifyContent="space-between" gap="3">
|
||||||
<Row alignItems="center" gap="3">
|
<Row alignItems="center" gap="3">
|
||||||
<Favicon domain={domain} />
|
<Favicon domain={domain} />
|
||||||
<Heading>
|
<Heading>{name}</Heading>
|
||||||
{name}
|
|
||||||
<ActiveUsers websiteId={websiteId} />
|
|
||||||
</Heading>
|
|
||||||
</Row>
|
</Row>
|
||||||
|
<ActiveUsers websiteId={websiteId} />
|
||||||
<Row alignItems="center" gap="3">
|
<Row alignItems="center" gap="3">
|
||||||
{showFilter && <WebsiteFilterButton websiteId={websiteId} />}
|
{showFilter && <WebsiteFilterButton websiteId={websiteId} />}
|
||||||
<WebsiteDateFilter websiteId={websiteId} />
|
<WebsiteDateFilter websiteId={websiteId} />
|
||||||
{allowEdit && (
|
{allowEdit && <WebsiteMenu websiteId={websiteId} />}
|
||||||
<MenuTrigger>
|
|
||||||
<Button variant="quiet">
|
|
||||||
<Icon>
|
|
||||||
<Icons.More />
|
|
||||||
</Icon>
|
|
||||||
</Button>
|
|
||||||
<Popover placement="bottom end">
|
|
||||||
<Menu>
|
|
||||||
<MenuItem>Compare dates</MenuItem>
|
|
||||||
<MenuItem>Share</MenuItem>
|
|
||||||
<MenuSeparator />
|
|
||||||
<MenuItem>Settings</MenuItem>
|
|
||||||
</Menu>
|
|
||||||
</Popover>
|
|
||||||
</MenuTrigger>
|
|
||||||
)}
|
|
||||||
</Row>
|
</Row>
|
||||||
</Row>
|
</Row>
|
||||||
{compareMode && items.map(item => <div key={item.value}>{item.label}</div>)}
|
<WebsiteCompareBar websiteId={websiteId} />
|
||||||
<FilterBar websiteId={websiteId} />
|
<FilterBar websiteId={websiteId} />
|
||||||
<WebsiteTabs websiteId={websiteId} />
|
<WebsiteTabs websiteId={websiteId} />
|
||||||
</Column>
|
</Column>
|
||||||
|
|
|
||||||
49
src/app/(main)/websites/[websiteId]/WebsiteMenu.tsx
Normal file
49
src/app/(main)/websites/[websiteId]/WebsiteMenu.tsx
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Icon,
|
||||||
|
Icons,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
MenuSeparator,
|
||||||
|
MenuTrigger,
|
||||||
|
Popover,
|
||||||
|
Text,
|
||||||
|
} from '@umami/react-zen';
|
||||||
|
import { Fragment } from 'react';
|
||||||
|
import { Lucide } from '@/components/icons';
|
||||||
|
import { useMessages } from '@/components/hooks';
|
||||||
|
|
||||||
|
export function WebsiteMenu({ websiteId }: { websiteId: string }) {
|
||||||
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
|
const menuItems = [
|
||||||
|
{ label: formatMessage(labels.compare), icon: <Lucide.GitCompare /> },
|
||||||
|
{ label: formatMessage(labels.share), icon: <Lucide.Share /> },
|
||||||
|
{ label: formatMessage(labels.edit), icon: <Lucide.Edit />, seperator: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuTrigger>
|
||||||
|
<Button variant="quiet">
|
||||||
|
<Icon>
|
||||||
|
<Icons.More />
|
||||||
|
</Icon>
|
||||||
|
</Button>
|
||||||
|
<Popover placement="bottom end">
|
||||||
|
<Menu>
|
||||||
|
{menuItems.map(({ label, icon, seperator }, index) => {
|
||||||
|
return (
|
||||||
|
<Fragment key={index}>
|
||||||
|
{seperator && <MenuSeparator />}
|
||||||
|
<MenuItem>
|
||||||
|
<Icon>{icon}</Icon>
|
||||||
|
<Text>{label}</Text>
|
||||||
|
</MenuItem>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Menu>
|
||||||
|
</Popover>
|
||||||
|
</MenuTrigger>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
export * from './queries/useActiveUsersQuery';
|
||||||
export * from './queries/useEventDataEventsQuery';
|
export * from './queries/useEventDataEventsQuery';
|
||||||
export * from './queries/useEventDataPropertiesQuery';
|
export * from './queries/useEventDataPropertiesQuery';
|
||||||
export * from './queries/useEventDataValuesQuery';
|
export * from './queries/useEventDataValuesQuery';
|
||||||
|
|
|
||||||
15
src/components/hooks/queries/useActiveUsersQuery.ts
Normal file
15
src/components/hooks/queries/useActiveUsersQuery.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { useApi } from '../useApi';
|
||||||
|
import { UseQueryOptions } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
export function useActyiveUsersQuery(
|
||||||
|
websiteId: string,
|
||||||
|
options?: Omit<UseQueryOptions, 'queryKey' | 'queryFn'>,
|
||||||
|
) {
|
||||||
|
const { get, useQuery } = useApi();
|
||||||
|
return useQuery<any>({
|
||||||
|
queryKey: ['websites:active', websiteId],
|
||||||
|
queryFn: () => get(`/websites/${websiteId}/active`),
|
||||||
|
enabled: !!websiteId,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -7,12 +7,7 @@ import { DateRange } from '@/lib/types';
|
||||||
import { useLocale } from './useLocale';
|
import { useLocale } from './useLocale';
|
||||||
import { useApi } from './useApi';
|
import { useApi } from './useApi';
|
||||||
|
|
||||||
export function useDateRange(websiteId?: string): {
|
export function useDateRange(websiteId?: string) {
|
||||||
dateRange: DateRange;
|
|
||||||
saveDateRange: (value: string | DateRange) => void;
|
|
||||||
dateCompare: string;
|
|
||||||
saveDateCompare: (value: string) => void;
|
|
||||||
} {
|
|
||||||
const { get } = useApi();
|
const { get } = useApi();
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
const websiteConfig = useWebsites(state => state[websiteId]?.dateRange);
|
const websiteConfig = useWebsites(state => state[websiteId]?.dateRange);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,5 @@ import { WebsiteContext } from '@/app/(main)/websites/[websiteId]/WebsiteProvide
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
|
|
||||||
export function useWebsite() {
|
export function useWebsite() {
|
||||||
const website = useContext(WebsiteContext);
|
return useContext(WebsiteContext);
|
||||||
|
|
||||||
return website;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
.dropdown span {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
@ -95,7 +95,7 @@ export function DateFilter({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Select
|
<Select
|
||||||
value={value}
|
selectedKey={value}
|
||||||
placeholder={formatMessage(labels.selectDate)}
|
placeholder={formatMessage(labels.selectDate)}
|
||||||
onSelectionChange={handleChange}
|
onSelectionChange={handleChange}
|
||||||
>
|
>
|
||||||
|
|
@ -103,9 +103,7 @@ export function DateFilter({
|
||||||
return (
|
return (
|
||||||
<Fragment key={label}>
|
<Fragment key={label}>
|
||||||
{divider && <ListSeparator />}
|
{divider && <ListSeparator />}
|
||||||
<ListItem key={label} id={value}>
|
<ListItem id={value}>{label}</ListItem>
|
||||||
{label}
|
|
||||||
</ListItem>
|
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border: 1px solid var(--base400);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
.input {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popup {
|
|
||||||
border: 1px solid var(--base400);
|
|
||||||
background: var(--base50);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
padding: 20px;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +1,15 @@
|
||||||
import { useRef } from 'react';
|
|
||||||
import { Row, Text, Icon, Button, MenuTrigger, Popover, Menu, MenuItem } from '@umami/react-zen';
|
import { Row, Text, Icon, Button, MenuTrigger, Popover, Menu, MenuItem } from '@umami/react-zen';
|
||||||
import { startOfMonth, endOfMonth, startOfYear, addMonths, subYears } from 'date-fns';
|
import { startOfMonth, endOfMonth, startOfYear, addMonths, subYears } from 'date-fns';
|
||||||
import { Icons } from '@/components/icons';
|
import { Icons } from '@/components/icons';
|
||||||
import { useLocale } from '@/components/hooks';
|
import { useLocale } from '@/components/hooks';
|
||||||
import { formatDate } from '@/lib/date';
|
import { formatDate } from '@/lib/date';
|
||||||
import styles from './MonthSelect.module.css';
|
|
||||||
|
|
||||||
export function MonthSelect({ date = new Date(), onChange }) {
|
export function MonthSelect({ date = new Date(), onChange }) {
|
||||||
const { locale, dateLocale } = useLocale();
|
const { locale } = useLocale();
|
||||||
const month = formatDate(date, 'MMMM', locale);
|
const month = formatDate(date, 'MMMM', locale);
|
||||||
const year = date.getFullYear();
|
const year = date.getFullYear();
|
||||||
const ref = useRef();
|
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const handleChange = (close: () => void, date: Date) => {
|
const handleChange = (close: () => void, date: Date) => {
|
||||||
onChange(`range:${startOfMonth(date).getTime()}:${endOfMonth(date).getTime()}`);
|
onChange(`range:${startOfMonth(date).getTime()}:${endOfMonth(date).getTime()}`);
|
||||||
close();
|
close();
|
||||||
|
|
@ -30,32 +28,36 @@ export function MonthSelect({ date = new Date(), onChange }) {
|
||||||
return (
|
return (
|
||||||
<Row>
|
<Row>
|
||||||
<MenuTrigger>
|
<MenuTrigger>
|
||||||
<Button className={styles.input} variant="quiet">
|
<Button variant="quiet">
|
||||||
<Text>{month}</Text>
|
<Text>{month}</Text>
|
||||||
<Icon size="sm">
|
<Icon size="sm">
|
||||||
<Icons.Chevron />
|
<Icons.Chevron />
|
||||||
</Icon>
|
</Icon>
|
||||||
</Button>
|
</Button>
|
||||||
<Popover>
|
<Popover>
|
||||||
<Menu items={months}>
|
<Menu>
|
||||||
{month => {
|
{months.map(month => {
|
||||||
return <MenuItem id={month}>{month.getDay()}</MenuItem>;
|
return (
|
||||||
}}
|
<MenuItem key={month.toString()} id={month.toString()}>
|
||||||
|
{month.getDay()}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</Menu>
|
</Menu>
|
||||||
</Popover>
|
</Popover>
|
||||||
</MenuTrigger>
|
</MenuTrigger>
|
||||||
<MenuTrigger>
|
<MenuTrigger>
|
||||||
<Button className={styles.input} variant="quiet">
|
<Button variant="quiet">
|
||||||
<Text>{year}</Text>
|
<Text>{year}</Text>
|
||||||
<Icon size="sm">
|
<Icon size="sm">
|
||||||
<Icons.Chevron />
|
<Icons.Chevron />
|
||||||
</Icon>
|
</Icon>
|
||||||
</Button>
|
</Button>
|
||||||
<Popover>
|
<Popover>
|
||||||
<Menu items={years}>
|
<Menu>
|
||||||
{year => {
|
{years.map(year => {
|
||||||
return <MenuItem id={year}>{year}</MenuItem>;
|
return <MenuItem id={year}>{year}</MenuItem>;
|
||||||
}}
|
})}
|
||||||
</Menu>
|
</Menu>
|
||||||
</Popover>
|
</Popover>
|
||||||
</MenuTrigger>
|
</MenuTrigger>
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
.menu {
|
|
||||||
width: 200px;
|
|
||||||
z-index: var(--z-index-popup);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
overflow: hidden;
|
|
||||||
background: var(--base50);
|
|
||||||
}
|
|
||||||
|
|
||||||
.item {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
background: var(--base50);
|
|
||||||
}
|
|
||||||
|
|
||||||
.version {
|
|
||||||
font-family: monospace;
|
|
||||||
font-size: 11px;
|
|
||||||
color: var(--base600);
|
|
||||||
text-align: right;
|
|
||||||
margin-inline-end: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
|
||||||
color: var(--font-color200);
|
|
||||||
background: var(--base75);
|
|
||||||
padding: var(--size300) var(--size600);
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
.popup {
|
|
||||||
background: var(--base50);
|
|
||||||
border: 1px solid var(--base500);
|
|
||||||
border-radius: 4px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
right: 0;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +1,27 @@
|
||||||
import { Button, Icon, PopupTrigger, Popup, Form, FormRow } from '@umami/react-zen';
|
import { Button, Icon, DialogTrigger, Popover, Column, Label } from '@umami/react-zen';
|
||||||
import { TimezoneSetting } from '@/app/(main)/profile/TimezoneSetting';
|
import { TimezoneSetting } from '@/app/(main)/profile/TimezoneSetting';
|
||||||
import { DateRangeSetting } from '@/app/(main)/profile/DateRangeSetting';
|
import { DateRangeSetting } from '@/app/(main)/profile/DateRangeSetting';
|
||||||
import { Icons } from '@/components/icons';
|
import { Icons } from '@/components/icons';
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages } from '@/components/hooks';
|
||||||
import styles from './SettingsButton.module.css';
|
|
||||||
|
|
||||||
export function SettingsButton() {
|
export function SettingsButton() {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PopupTrigger>
|
<DialogTrigger>
|
||||||
<Button variant="quiet">
|
<Button variant="quiet">
|
||||||
<Icon>
|
<Icon>
|
||||||
<Icons.Gear />
|
<Icons.Gear />
|
||||||
</Icon>
|
</Icon>
|
||||||
</Button>
|
</Button>
|
||||||
<Popup className={styles.popup} position="bottom" alignment="end">
|
<Popover placement="bottom end">
|
||||||
<Form>
|
<Column gap="3">
|
||||||
<FormRow label={formatMessage(labels.timezone)}>
|
<Label>{formatMessage(labels.timezone)}</Label>
|
||||||
<TimezoneSetting />
|
<TimezoneSetting />
|
||||||
</FormRow>
|
<Label>{formatMessage(labels.defaultDateRange)}</Label>
|
||||||
<FormRow label={formatMessage(labels.defaultDateRange)}>
|
<DateRangeSetting />
|
||||||
<DateRangeSetting />
|
</Column>
|
||||||
</FormRow>
|
</Popover>
|
||||||
</Form>
|
</DialogTrigger>
|
||||||
</Popup>
|
|
||||||
</PopupTrigger>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
.button {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu {
|
|
||||||
background: var(--base50);
|
|
||||||
min-width: 260px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.heading {
|
|
||||||
color: var(--base600);
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: 700;
|
|
||||||
padding: 8px 16px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
border-bottom: 1px solid var(--base300);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown {
|
|
||||||
min-width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons button:first-child {
|
|
||||||
border-start-end-radius: 0;
|
|
||||||
border-end-end-radius: 0;
|
|
||||||
border-inline-end: 1px solid var(--base400);
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons button:last-child {
|
|
||||||
border-start-start-radius: 0;
|
|
||||||
border-end-start-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
@ -18,6 +18,7 @@ export function WebsiteDateFilter({
|
||||||
value === 'all' || isAfter(getOffsetDateRange(dateRange, 1).startDate, new Date());
|
value === 'all' || isAfter(getOffsetDateRange(dateRange, 1).startDate, new Date());
|
||||||
|
|
||||||
const handleChange = (value: string | DateRange) => {
|
const handleChange = (value: string | DateRange) => {
|
||||||
|
console.log('WebsiteDateFilter', value);
|
||||||
saveDateRange(value);
|
saveDateRange(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -25,6 +26,8 @@ export function WebsiteDateFilter({
|
||||||
saveDateRange(getOffsetDateRange(dateRange, increment));
|
saveDateRange(getOffsetDateRange(dateRange, increment));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log({ dateRange, disableForward });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row gap="3">
|
<Row gap="3">
|
||||||
{value !== 'all' && !value.startsWith('range') && (
|
{value !== 'all' && !value.startsWith('range') && (
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
.dropdown {
|
|
||||||
max-height: 400px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
@ -51,6 +51,7 @@ export const labels = defineMessages({
|
||||||
data: { id: 'label.data', defaultMessage: 'Data' },
|
data: { id: 'label.data', defaultMessage: 'Data' },
|
||||||
trackingCode: { id: 'label.tracking-code', defaultMessage: 'Tracking code' },
|
trackingCode: { id: 'label.tracking-code', defaultMessage: 'Tracking code' },
|
||||||
shareUrl: { id: 'label.share-url', defaultMessage: 'Share URL' },
|
shareUrl: { id: 'label.share-url', defaultMessage: 'Share URL' },
|
||||||
|
share: { id: 'label.share', defaultMessage: 'Share' },
|
||||||
actions: { id: 'label.actions', defaultMessage: 'Actions' },
|
actions: { id: 'label.actions', defaultMessage: 'Actions' },
|
||||||
domain: { id: 'label.domain', defaultMessage: 'Domain' },
|
domain: { id: 'label.domain', defaultMessage: 'Domain' },
|
||||||
websiteId: { id: 'label.website-id', defaultMessage: 'Website ID' },
|
websiteId: { id: 'label.website-id', defaultMessage: 'Website ID' },
|
||||||
|
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-inline-start: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
display: flex;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-size: var(--font-size-md);
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { StatusLight } from '@umami/react-zen';
|
import { Text, StatusLight } from '@umami/react-zen';
|
||||||
import { useApi } from '@/components/hooks';
|
import { useMessages, useActyiveUsersQuery } from '@/components/hooks';
|
||||||
import { useMessages } from '@/components/hooks';
|
|
||||||
import styles from './ActiveUsers.module.css';
|
|
||||||
|
|
||||||
export function ActiveUsers({
|
export function ActiveUsers({
|
||||||
websiteId,
|
websiteId,
|
||||||
|
|
@ -14,13 +12,7 @@ export function ActiveUsers({
|
||||||
refetchInterval?: number;
|
refetchInterval?: number;
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage, messages } = useMessages();
|
const { formatMessage, messages } = useMessages();
|
||||||
const { get, useQuery } = useApi();
|
const { data } = useActyiveUsersQuery(websiteId, { refetchInterval });
|
||||||
const { data } = useQuery({
|
|
||||||
queryKey: ['websites:active', websiteId],
|
|
||||||
queryFn: () => get(`/websites/${websiteId}/active`),
|
|
||||||
enabled: !!websiteId,
|
|
||||||
refetchInterval,
|
|
||||||
});
|
|
||||||
|
|
||||||
const count = useMemo(() => {
|
const count = useMemo(() => {
|
||||||
if (websiteId) {
|
if (websiteId) {
|
||||||
|
|
@ -35,8 +27,8 @@ export function ActiveUsers({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StatusLight className={styles.container} variant="success">
|
<StatusLight variant="success">
|
||||||
<div className={styles.text}>{formatMessage(messages.activeUsers, { x: count })}</div>
|
<Text size="2">{formatMessage(messages.activeUsers, { x: count })}</Text>
|
||||||
</StatusLight>
|
</StatusLight>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue