mirror of
https://github.com/umami-software/umami.git
synced 2026-02-14 17:45:38 +01:00
Updated date range handling.
This commit is contained in:
parent
6d1603fa28
commit
5ca51b3e8f
19 changed files with 101 additions and 99 deletions
|
|
@ -80,7 +80,7 @@
|
||||||
"@react-spring/web": "^9.7.3",
|
"@react-spring/web": "^9.7.3",
|
||||||
"@svgr/cli": "^8.1.0",
|
"@svgr/cli": "^8.1.0",
|
||||||
"@tanstack/react-query": "^5.80.10",
|
"@tanstack/react-query": "^5.80.10",
|
||||||
"@umami/react-zen": "^0.139.0",
|
"@umami/react-zen": "^0.142.0",
|
||||||
"@umami/redis-client": "^0.27.0",
|
"@umami/redis-client": "^0.27.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"chalk": "^4.1.1",
|
"chalk": "^4.1.1",
|
||||||
|
|
|
||||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
|
|
@ -45,8 +45,8 @@ importers:
|
||||||
specifier: ^5.80.10
|
specifier: ^5.80.10
|
||||||
version: 5.80.10(react@19.1.0)
|
version: 5.80.10(react@19.1.0)
|
||||||
'@umami/react-zen':
|
'@umami/react-zen':
|
||||||
specifier: ^0.139.0
|
specifier: ^0.142.0
|
||||||
version: 0.139.0(@babel/core@7.27.1)(@types/react@19.1.8)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0))
|
version: 0.142.0(@babel/core@7.27.1)(@types/react@19.1.8)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0))
|
||||||
'@umami/redis-client':
|
'@umami/redis-client':
|
||||||
specifier: ^0.27.0
|
specifier: ^0.27.0
|
||||||
version: 0.27.0
|
version: 0.27.0
|
||||||
|
|
@ -2549,8 +2549,8 @@ packages:
|
||||||
resolution: {integrity: sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==}
|
resolution: {integrity: sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
'@umami/react-zen@0.139.0':
|
'@umami/react-zen@0.142.0':
|
||||||
resolution: {integrity: sha512-NRf27+05z78DLFxK3aQUBfhZW7covl6qtS4OcaBUbZ71VZ7eeRVg7SU7Cn3NvkXlcI16t6bbLXGW4HjvfBhXsw==}
|
resolution: {integrity: sha512-xD0O96c1AsztIbD8DZOszBeXAmEhVJ1isqsms+Nu/Kzf4vkWhEPpu7dbhntroHiA7JLK/uI1bgYHQMJFMM1f4w==}
|
||||||
|
|
||||||
'@umami/redis-client@0.27.0':
|
'@umami/redis-client@0.27.0':
|
||||||
resolution: {integrity: sha512-SbHTpxhgeZyTBUSp2zdZM+XUtpsaSL4Tad8QXIEhEtjWhvvfoornyT5kLuyYCVtzSAT4daALeGmOO1z6EE1KcA==}
|
resolution: {integrity: sha512-SbHTpxhgeZyTBUSp2zdZM+XUtpsaSL4Tad8QXIEhEtjWhvvfoornyT5kLuyYCVtzSAT4daALeGmOO1z6EE1KcA==}
|
||||||
|
|
@ -9733,7 +9733,7 @@ snapshots:
|
||||||
'@typescript-eslint/types': 8.34.1
|
'@typescript-eslint/types': 8.34.1
|
||||||
eslint-visitor-keys: 4.2.1
|
eslint-visitor-keys: 4.2.1
|
||||||
|
|
||||||
'@umami/react-zen@0.139.0(@babel/core@7.27.1)(@types/react@19.1.8)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0))':
|
'@umami/react-zen@0.142.0(@babel/core@7.27.1)(@types/react@19.1.8)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@fontsource/jetbrains-mono': 5.2.6
|
'@fontsource/jetbrains-mono': 5.2.6
|
||||||
'@internationalized/date': 3.8.2
|
'@internationalized/date': 3.8.2
|
||||||
|
|
|
||||||
|
|
@ -19,32 +19,32 @@ export function SideNav(props: any) {
|
||||||
const links = [
|
const links = [
|
||||||
{
|
{
|
||||||
label: formatMessage(labels.websites),
|
label: formatMessage(labels.websites),
|
||||||
href: renderUrl('/websites'),
|
href: '/websites',
|
||||||
icon: <Globe />,
|
icon: <Globe />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: formatMessage(labels.boards),
|
label: formatMessage(labels.boards),
|
||||||
href: renderUrl('/boards'),
|
href: '/boards',
|
||||||
icon: <LayoutDashboard />,
|
icon: <LayoutDashboard />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: formatMessage(labels.links),
|
label: formatMessage(labels.links),
|
||||||
href: renderUrl('/links'),
|
href: '/links',
|
||||||
icon: <LinkIcon />,
|
icon: <LinkIcon />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: formatMessage(labels.pixels),
|
label: formatMessage(labels.pixels),
|
||||||
href: renderUrl('/pixels'),
|
href: '/pixels',
|
||||||
icon: <Grid2X2 />,
|
icon: <Grid2X2 />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: formatMessage(labels.settings),
|
label: formatMessage(labels.settings),
|
||||||
href: renderUrl('/settings'),
|
href: '/settings',
|
||||||
icon: <Settings />,
|
icon: <Settings />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: formatMessage(labels.admin),
|
label: formatMessage(labels.admin),
|
||||||
href: renderUrl('/admin'),
|
href: '/admin',
|
||||||
icon: <LockKeyhole />,
|
icon: <LockKeyhole />,
|
||||||
},
|
},
|
||||||
].filter(n => n);
|
].filter(n => n);
|
||||||
|
|
@ -57,7 +57,7 @@ export function SideNav(props: any) {
|
||||||
<SidebarSection>
|
<SidebarSection>
|
||||||
{links.map(({ href, label, icon }) => {
|
{links.map(({ href, label, icon }) => {
|
||||||
return (
|
return (
|
||||||
<Link key={href} href={href} role="button">
|
<Link key={href} href={renderUrl(href, false)} role="button">
|
||||||
<SidebarItem label={label} icon={icon} isSelected={pathname.startsWith(href)} />
|
<SidebarItem label={label} icon={icon} isSelected={pathname.startsWith(href)} />
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
import { DateFilter } from '@/components/input/DateFilter';
|
import { DateFilter } from '@/components/input/DateFilter';
|
||||||
import { Button, Row } from '@umami/react-zen';
|
import { Button, Row } from '@umami/react-zen';
|
||||||
import { useDateRange, useMessages } from '@/components/hooks';
|
import { useDateRange, useMessages } from '@/components/hooks';
|
||||||
import { DEFAULT_DATE_RANGE } from '@/lib/constants';
|
import { DEFAULT_DATE_RANGE_VALUE } from '@/lib/constants';
|
||||||
import { DateRange } from '@/lib/types';
|
|
||||||
|
|
||||||
export function DateRangeSetting() {
|
export function DateRangeSetting() {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { dateRange, saveDateRange } = useDateRange();
|
const { dateRange, saveDateRange } = useDateRange();
|
||||||
const { value } = dateRange;
|
const { value } = dateRange;
|
||||||
|
|
||||||
const handleChange = (value: string | DateRange) => saveDateRange(value);
|
const handleChange = (value: string) => saveDateRange(value);
|
||||||
const handleReset = () => saveDateRange(DEFAULT_DATE_RANGE);
|
const handleReset = () => saveDateRange(DEFAULT_DATE_RANGE_VALUE);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row gap="3">
|
<Row gap="3">
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ export function ProfileSettings() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column gap="6">
|
<Column width="400px" gap="6">
|
||||||
<Column>
|
<Column>
|
||||||
<Label>{formatMessage(labels.username)}</Label>
|
<Label>{formatMessage(labels.username)}</Label>
|
||||||
{username}
|
{username}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { useMessages } from '@/components/hooks';
|
||||||
import { Globe, Arrow } from '@/components/icons';
|
import { Globe, Arrow } from '@/components/icons';
|
||||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||||
import { WebsiteShareForm } from './WebsiteShareForm';
|
import { WebsiteShareForm } from './WebsiteShareForm';
|
||||||
import { TrackingCode } from './TrackingCode';
|
import { WebsiteTrackingCode } from './WebsiteTrackingCode';
|
||||||
import { WebsiteData } from './WebsiteData';
|
import { WebsiteData } from './WebsiteData';
|
||||||
import { WebsiteEditForm } from './WebsiteEditForm';
|
import { WebsiteEditForm } from './WebsiteEditForm';
|
||||||
import { LinkButton } from '@/components/common/LinkButton';
|
import { LinkButton } from '@/components/common/LinkButton';
|
||||||
|
|
@ -23,11 +23,7 @@ export function WebsiteSettings({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SectionHeader title={website?.name} icon={<Globe />}>
|
<SectionHeader title={website?.name} icon={<Globe />}>
|
||||||
<LinkButton
|
<LinkButton href={`/websites/${websiteId}`} target={openExternal ? '_blank' : null}>
|
||||||
variant="primary"
|
|
||||||
href={`/websites/${websiteId}`}
|
|
||||||
target={openExternal ? '_blank' : null}
|
|
||||||
>
|
|
||||||
<Icon>
|
<Icon>
|
||||||
<Arrow />
|
<Arrow />
|
||||||
</Icon>
|
</Icon>
|
||||||
|
|
@ -45,10 +41,10 @@ export function WebsiteSettings({
|
||||||
<WebsiteEditForm websiteId={websiteId} />
|
<WebsiteEditForm websiteId={websiteId} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel id="tracking">
|
<TabPanel id="tracking">
|
||||||
<TrackingCode websiteId={websiteId} />
|
<WebsiteTrackingCode websiteId={websiteId} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel id="share">
|
<TabPanel id="share">
|
||||||
<WebsiteShareForm />
|
<WebsiteShareForm websiteId={websiteId} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel id="data">
|
<TabPanel id="data">
|
||||||
<WebsiteData websiteId={websiteId} />
|
<WebsiteData websiteId={websiteId} />
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ const generateId = () => getRandomChars(16);
|
||||||
|
|
||||||
export interface WebsiteShareFormProps {
|
export interface WebsiteShareFormProps {
|
||||||
websiteId: string;
|
websiteId: string;
|
||||||
shareId: string;
|
shareId?: string;
|
||||||
onSave?: () => void;
|
onSave?: () => void;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,15 @@
|
||||||
import { TextField } from '@umami/react-zen';
|
import { TextField, Text, Column } from '@umami/react-zen';
|
||||||
import { useMessages, useConfig } from '@/components/hooks';
|
import { useMessages, useConfig } from '@/components/hooks';
|
||||||
|
|
||||||
const SCRIPT_NAME = 'script.js';
|
const SCRIPT_NAME = 'script.js';
|
||||||
|
|
||||||
export function TrackingCode({ websiteId, hostUrl }: { websiteId: string; hostUrl?: string }) {
|
export function WebsiteTrackingCode({
|
||||||
|
websiteId,
|
||||||
|
hostUrl,
|
||||||
|
}: {
|
||||||
|
websiteId: string;
|
||||||
|
hostUrl?: string;
|
||||||
|
}) {
|
||||||
const { formatMessage, messages } = useMessages();
|
const { formatMessage, messages } = useMessages();
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
|
|
||||||
|
|
@ -19,9 +25,9 @@ export function TrackingCode({ websiteId, hostUrl }: { websiteId: string; hostUr
|
||||||
const code = `<script defer src="${url}" data-website-id="${websiteId}"></script>`;
|
const code = `<script defer src="${url}" data-website-id="${websiteId}"></script>`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Column gap>
|
||||||
<p>{formatMessage(messages.trackingCode)}</p>
|
<Text>{formatMessage(messages.trackingCode)}</Text>
|
||||||
<TextField value={code} isReadOnly allowCopy asTextArea />
|
<TextField value={code} isReadOnly allowCopy asTextArea resize="none" />
|
||||||
</>
|
</Column>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -5,22 +5,24 @@ import { Share, Edit } from '@/components/icons';
|
||||||
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 { WebsiteShareForm } from '@/app/(main)/settings/websites/[websiteId]/WebsiteShareForm';
|
import { WebsiteShareForm } from '@/app/(main)/settings/websites/[websiteId]/WebsiteShareForm';
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages, useNavigation } from '@/components/hooks';
|
||||||
|
import { LinkButton } from '@/components/common/LinkButton';
|
||||||
|
|
||||||
export function WebsiteHeader() {
|
export function WebsiteHeader() {
|
||||||
const website = useWebsite();
|
const website = useWebsite();
|
||||||
|
const { renderUrl } = useNavigation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageHeader title={website.name} icon={<Favicon domain={website.domain} />} showBorder={false}>
|
<PageHeader title={website.name} icon={<Favicon domain={website.domain} />} showBorder={false}>
|
||||||
<Row alignItems="center" gap>
|
<Row alignItems="center" gap>
|
||||||
<ActiveUsers websiteId={website.id} />
|
<ActiveUsers websiteId={website.id} />
|
||||||
<ShareButton websiteId={website.id} shareId={website.shareId} />
|
<ShareButton websiteId={website.id} shareId={website.shareId} />
|
||||||
<Button>
|
<LinkButton href={renderUrl(`/settings/websites/${website.id}`)}>
|
||||||
<Icon>
|
<Icon>
|
||||||
<Edit />
|
<Edit />
|
||||||
</Icon>
|
</Icon>
|
||||||
<Text>Edit</Text>
|
<Text>Edit</Text>
|
||||||
</Button>
|
</LinkButton>
|
||||||
</Row>
|
</Row>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,55 +1,53 @@
|
||||||
import { getMinimumUnit, parseDateRange } from '@/lib/date';
|
import { getMinimumUnit, parseDateRange, getOffsetDateRange } from '@/lib/date';
|
||||||
import { setItem } from '@/lib/storage';
|
import { setItem } from '@/lib/storage';
|
||||||
import { DATE_RANGE_CONFIG, DEFAULT_DATE_COMPARE, DEFAULT_DATE_RANGE } from '@/lib/constants';
|
import { DATE_RANGE_CONFIG, DEFAULT_DATE_COMPARE, DEFAULT_DATE_RANGE_VALUE } from '@/lib/constants';
|
||||||
import { setWebsiteDateCompare, setWebsiteDateRange, useWebsites } from '@/store/websites';
|
import { setWebsiteDateCompare, setWebsiteDateRange, useWebsites } from '@/store/websites';
|
||||||
import { setDateRange, useApp } from '@/store/app';
|
import { setDateRangeValue, useApp } from '@/store/app';
|
||||||
import { DateRange } from '@/lib/types';
|
|
||||||
import { useLocale } from './useLocale';
|
import { useLocale } from './useLocale';
|
||||||
import { useApi } from './useApi';
|
import { useApi } from './useApi';
|
||||||
import { useNavigation } from './useNavigation';
|
import { useNavigation } from './useNavigation';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
export function useDateRange(websiteId?: string) {
|
export function useDateRange(websiteId?: string) {
|
||||||
const { get } = useApi();
|
const { get } = useApi();
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
const {
|
const {
|
||||||
query: { date },
|
query: { date, offset = 0 },
|
||||||
} = useNavigation();
|
} = useNavigation();
|
||||||
const websiteConfig = useWebsites(state => state[websiteId]?.dateRange);
|
const websiteConfig = useWebsites(state => state[websiteId]?.dateRange);
|
||||||
const globalConfig = useApp(state => state.dateRange);
|
const globalConfig = useApp(state => state.dateRangeValue);
|
||||||
const dateRange = parseDateRange(
|
const dateRangeObject = parseDateRange(
|
||||||
date || websiteConfig || globalConfig || DEFAULT_DATE_RANGE,
|
date || websiteConfig?.value || globalConfig || DEFAULT_DATE_RANGE_VALUE,
|
||||||
locale,
|
locale,
|
||||||
);
|
);
|
||||||
|
const dateRange = useMemo(
|
||||||
|
() => (offset ? getOffsetDateRange(dateRangeObject, +offset) : dateRangeObject),
|
||||||
|
[date, offset],
|
||||||
|
);
|
||||||
const dateCompare = useWebsites(state => state[websiteId]?.dateCompare || DEFAULT_DATE_COMPARE);
|
const dateCompare = useWebsites(state => state[websiteId]?.dateCompare || DEFAULT_DATE_COMPARE);
|
||||||
|
|
||||||
const saveDateRange = async (value: DateRange | string) => {
|
const saveDateRange = async (value: string) => {
|
||||||
if (websiteId) {
|
if (websiteId) {
|
||||||
let dateRange: DateRange | string = value;
|
if (value === 'all') {
|
||||||
|
const result: any = await get(`/websites/${websiteId}/daterange`);
|
||||||
|
const { mindate, maxdate } = result;
|
||||||
|
|
||||||
if (typeof value === 'string') {
|
const startDate = new Date(mindate);
|
||||||
if (value === 'all') {
|
const endDate = new Date(maxdate);
|
||||||
const result: any = await get(`/websites/${websiteId}/daterange`);
|
const unit = getMinimumUnit(startDate, endDate);
|
||||||
const { mindate, maxdate } = result;
|
|
||||||
|
|
||||||
const startDate = new Date(mindate);
|
setWebsiteDateRange(websiteId, {
|
||||||
const endDate = new Date(maxdate);
|
startDate,
|
||||||
const unit = getMinimumUnit(startDate, endDate);
|
endDate,
|
||||||
|
unit,
|
||||||
dateRange = {
|
value,
|
||||||
startDate,
|
});
|
||||||
endDate,
|
} else {
|
||||||
unit,
|
setWebsiteDateRange(websiteId, parseDateRange(value, locale));
|
||||||
value,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
dateRange = parseDateRange(value, locale);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setWebsiteDateRange(websiteId, dateRange as DateRange);
|
|
||||||
} else {
|
} else {
|
||||||
setItem(DATE_RANGE_CONFIG, value);
|
setItem(DATE_RANGE_CONFIG, value);
|
||||||
setDateRange(value);
|
setDateRangeValue(value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ export interface DateFilterProps {
|
||||||
endDate: Date;
|
endDate: Date;
|
||||||
onChange?: (value: string) => void;
|
onChange?: (value: string) => void;
|
||||||
showAllTime?: boolean;
|
showAllTime?: boolean;
|
||||||
|
renderDate?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DateFilter({
|
export function DateFilter({
|
||||||
|
|
@ -18,7 +19,8 @@ export function DateFilter({
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
onChange,
|
onChange,
|
||||||
showAllTime = false,
|
showAllTime,
|
||||||
|
renderDate,
|
||||||
}: DateFilterProps) {
|
}: DateFilterProps) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const [showPicker, setShowPicker] = useState(false);
|
const [showPicker, setShowPicker] = useState(false);
|
||||||
|
|
@ -89,7 +91,7 @@ export function DateFilter({
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderValue = ({ defaultChildren }) => {
|
const renderValue = ({ defaultChildren }) => {
|
||||||
return value?.startsWith('range') ? (
|
return value?.startsWith('range') || renderDate ? (
|
||||||
<DateDisplay startDate={startDate} endDate={endDate} />
|
<DateDisplay startDate={startDate} endDate={endDate} />
|
||||||
) : (
|
) : (
|
||||||
defaultChildren
|
defaultChildren
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import {
|
||||||
import { isAfter } from 'date-fns';
|
import { isAfter } from 'date-fns';
|
||||||
import { Chevron, Close, Compare } from '@/components/icons';
|
import { Chevron, Close, Compare } from '@/components/icons';
|
||||||
import { useDateRange, useMessages, useNavigation } from '@/components/hooks';
|
import { useDateRange, useMessages, useNavigation } from '@/components/hooks';
|
||||||
import { getOffsetDateRange } from '@/lib/date';
|
|
||||||
import { DateFilter } from './DateFilter';
|
import { DateFilter } from './DateFilter';
|
||||||
|
|
||||||
export function WebsiteDateFilter({
|
export function WebsiteDateFilter({
|
||||||
|
|
@ -26,13 +25,13 @@ export function WebsiteDateFilter({
|
||||||
showButtons?: boolean;
|
showButtons?: boolean;
|
||||||
allowCompare?: boolean;
|
allowCompare?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { dateRange, saveDateRange } = useDateRange(websiteId);
|
const { dateRange } = useDateRange(websiteId);
|
||||||
const { value, startDate, endDate, offset } = dateRange;
|
const { value, startDate, endDate } = dateRange;
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const {
|
const {
|
||||||
router,
|
router,
|
||||||
updateParams,
|
updateParams,
|
||||||
query: { compare },
|
query: { compare, offset = 0 },
|
||||||
} = useNavigation();
|
} = useNavigation();
|
||||||
const isAllTime = value === 'all';
|
const isAllTime = value === 'all';
|
||||||
const isCustomRange = value.startsWith('range');
|
const isCustomRange = value.startsWith('range');
|
||||||
|
|
@ -40,13 +39,11 @@ export function WebsiteDateFilter({
|
||||||
const disableForward = value === 'all' || isAfter(endDate, new Date());
|
const disableForward = value === 'all' || isAfter(endDate, new Date());
|
||||||
|
|
||||||
const handleChange = (date: string) => {
|
const handleChange = (date: string) => {
|
||||||
router.push(updateParams({ date }));
|
router.push(updateParams({ date, offset: undefined }));
|
||||||
saveDateRange(date);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleIncrement = (increment: number) => {
|
const handleIncrement = (increment: number) => {
|
||||||
router.push(updateParams({ offset: offset + increment }));
|
router.push(updateParams({ offset: +offset + increment }));
|
||||||
saveDateRange(getOffsetDateRange(dateRange, increment));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelect = (compare: any) => {
|
const handleSelect = (compare: any) => {
|
||||||
|
|
@ -79,6 +76,7 @@ export function WebsiteDateFilter({
|
||||||
endDate={endDate}
|
endDate={endDate}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
showAllTime={showAllTime}
|
showAllTime={showAllTime}
|
||||||
|
renderDate={+offset !== 0}
|
||||||
/>
|
/>
|
||||||
{!isAllTime && compare && (
|
{!isAllTime && compare && (
|
||||||
<Row alignItems="center" gap>
|
<Row alignItems="center" gap>
|
||||||
|
|
|
||||||
|
|
@ -330,6 +330,7 @@ export const labels = defineMessages({
|
||||||
conversion: { id: 'label.conversion', defaultMessage: 'Conversion' },
|
conversion: { id: 'label.conversion', defaultMessage: 'Conversion' },
|
||||||
firstClick: { id: 'label.first-click', defaultMessage: 'First click' },
|
firstClick: { id: 'label.first-click', defaultMessage: 'First click' },
|
||||||
lastClick: { id: 'label.last-click', defaultMessage: 'Last click' },
|
lastClick: { id: 'label.last-click', defaultMessage: 'Last click' },
|
||||||
|
online: { id: 'label.online', defaultMessage: 'Online' },
|
||||||
});
|
});
|
||||||
|
|
||||||
export const messages = defineMessages({
|
export const messages = defineMessages({
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export function ActiveUsers({
|
||||||
value?: number;
|
value?: number;
|
||||||
refetchInterval?: number;
|
refetchInterval?: number;
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage, messages } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { data } = useActyiveUsersQuery(websiteId, { refetchInterval });
|
const { data } = useActyiveUsersQuery(websiteId, { refetchInterval });
|
||||||
|
|
||||||
const count = useMemo(() => {
|
const count = useMemo(() => {
|
||||||
|
|
@ -28,8 +28,8 @@ export function ActiveUsers({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StatusLight variant="success">
|
<StatusLight variant="success">
|
||||||
<Text size="2" weight="bold">
|
<Text size="2" weight="medium">
|
||||||
{formatMessage(messages.numberOfUsers, { x: count })}
|
{count} {formatMessage(labels.online)}
|
||||||
</Text>
|
</Text>
|
||||||
</StatusLight>
|
</StatusLight>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export * from '@/app/(main)/settings/teams/TeamsTable';
|
||||||
export * from '@/app/(main)/settings/teams/WebsiteTags';
|
export * from '@/app/(main)/settings/teams/WebsiteTags';
|
||||||
|
|
||||||
export * from '@/app/(main)/settings/websites/[websiteId]/WebsiteShareForm';
|
export * from '@/app/(main)/settings/websites/[websiteId]/WebsiteShareForm';
|
||||||
export * from '@/app/(main)/settings/websites/[websiteId]/TrackingCode';
|
export * from '@/app/(main)/settings/websites/[websiteId]/WebsiteTrackingCode';
|
||||||
export * from '@/app/(main)/settings/websites/[websiteId]/WebsiteData';
|
export * from '@/app/(main)/settings/websites/[websiteId]/WebsiteData';
|
||||||
export * from '@/app/(main)/settings/websites/[websiteId]/WebsiteDeleteForm';
|
export * from '@/app/(main)/settings/websites/[websiteId]/WebsiteDeleteForm';
|
||||||
export * from '@/app/(main)/settings/websites/[websiteId]/WebsiteEditForm';
|
export * from '@/app/(main)/settings/websites/[websiteId]/WebsiteEditForm';
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export const FAVICON_URL = 'https://icons.duckduckgo.com/ip3/{{domain}}.ico';
|
||||||
export const DEFAULT_LOCALE = process.env.defaultLocale || 'en-US';
|
export const DEFAULT_LOCALE = process.env.defaultLocale || 'en-US';
|
||||||
export const DEFAULT_THEME = 'light';
|
export const DEFAULT_THEME = 'light';
|
||||||
export const DEFAULT_ANIMATION_DURATION = 300;
|
export const DEFAULT_ANIMATION_DURATION = 300;
|
||||||
export const DEFAULT_DATE_RANGE = '24hour';
|
export const DEFAULT_DATE_RANGE_VALUE = '24hour';
|
||||||
export const DEFAULT_WEBSITE_LIMIT = 10;
|
export const DEFAULT_WEBSITE_LIMIT = 10;
|
||||||
export const DEFAULT_RESET_DATE = '2000-01-01';
|
export const DEFAULT_RESET_DATE = '2000-01-01';
|
||||||
export const DEFAULT_PAGE_SIZE = 10;
|
export const DEFAULT_PAGE_SIZE = 10;
|
||||||
|
|
|
||||||
|
|
@ -126,9 +126,9 @@ export function parseDateValue(value: string) {
|
||||||
return { num: +num, unit };
|
return { num: +num, unit };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseDateRange(value: string | object, locale = 'en-US'): DateRange {
|
export function parseDateRange(value: string, locale = 'en-US'): DateRange {
|
||||||
if (typeof value !== 'string') {
|
if (typeof value !== 'string') {
|
||||||
return value as DateRange;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value === 'all') {
|
if (value === 'all') {
|
||||||
|
|
@ -151,14 +151,13 @@ export function parseDateRange(value: string | object, locale = 'en-US'): DateRa
|
||||||
endDate,
|
endDate,
|
||||||
value,
|
value,
|
||||||
...parseDateValue(value),
|
...parseDateValue(value),
|
||||||
offset: 0,
|
|
||||||
unit,
|
unit,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const dateLocale = getDateLocale(locale);
|
const dateLocale = getDateLocale(locale);
|
||||||
const { num, unit } = parseDateValue(value);
|
const { num = 1, unit } = parseDateValue(value);
|
||||||
|
|
||||||
switch (unit) {
|
switch (unit) {
|
||||||
case 'hour':
|
case 'hour':
|
||||||
|
|
@ -211,10 +210,14 @@ export function parseDateRange(value: string | object, locale = 'en-US'): DateRa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getOffsetDateRange(dateRange: DateRange, increment: number) {
|
export function getOffsetDateRange(dateRange: DateRange, offset: number) {
|
||||||
const { startDate, endDate, unit, num, offset, value } = dateRange;
|
if (offset === 0) {
|
||||||
|
return dateRange;
|
||||||
|
}
|
||||||
|
|
||||||
const change = num * increment;
|
const { startDate, endDate, unit, num, value } = dateRange;
|
||||||
|
|
||||||
|
const change = num * offset;
|
||||||
const { add } = DATE_FUNCTIONS[unit];
|
const { add } = DATE_FUNCTIONS[unit];
|
||||||
const { unit: originalUnit } = parseDateValue(value) || {};
|
const { unit: originalUnit } = parseDateValue(value) || {};
|
||||||
|
|
||||||
|
|
@ -224,28 +227,24 @@ export function getOffsetDateRange(dateRange: DateRange, increment: number) {
|
||||||
...dateRange,
|
...dateRange,
|
||||||
startDate: addDays(startDate, change),
|
startDate: addDays(startDate, change),
|
||||||
endDate: addDays(endDate, change),
|
endDate: addDays(endDate, change),
|
||||||
offset: offset + increment,
|
|
||||||
};
|
};
|
||||||
case 'week':
|
case 'week':
|
||||||
return {
|
return {
|
||||||
...dateRange,
|
...dateRange,
|
||||||
startDate: addWeeks(startDate, change),
|
startDate: addWeeks(startDate, change),
|
||||||
endDate: addWeeks(endDate, change),
|
endDate: addWeeks(endDate, change),
|
||||||
offset: offset + increment,
|
|
||||||
};
|
};
|
||||||
case 'month':
|
case 'month':
|
||||||
return {
|
return {
|
||||||
...dateRange,
|
...dateRange,
|
||||||
startDate: addMonths(startDate, change),
|
startDate: addMonths(startDate, change),
|
||||||
endDate: addMonths(endDate, change),
|
endDate: addMonths(endDate, change),
|
||||||
offset: offset + increment,
|
|
||||||
};
|
};
|
||||||
case 'year':
|
case 'year':
|
||||||
return {
|
return {
|
||||||
...dateRange,
|
...dateRange,
|
||||||
startDate: addYears(startDate, change),
|
startDate: addYears(startDate, change),
|
||||||
endDate: addYears(endDate, change),
|
endDate: addYears(endDate, change),
|
||||||
offset: offset + increment,
|
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
|
|
@ -254,7 +253,6 @@ export function getOffsetDateRange(dateRange: DateRange, increment: number) {
|
||||||
value,
|
value,
|
||||||
unit,
|
unit,
|
||||||
num,
|
num,
|
||||||
offset: offset + increment,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ export async function getActiveVisitors(...args: [websiteId: string]) {
|
||||||
|
|
||||||
async function relationalQuery(websiteId: string) {
|
async function relationalQuery(websiteId: string) {
|
||||||
const { rawQuery } = prisma;
|
const { rawQuery } = prisma;
|
||||||
|
const startDate = subMinutes(new Date(), 5);
|
||||||
|
|
||||||
const result = await rawQuery(
|
const result = await rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -20,7 +21,7 @@ async function relationalQuery(websiteId: string) {
|
||||||
where website_id = {{websiteId::uuid}}
|
where website_id = {{websiteId::uuid}}
|
||||||
and created_at >= {{startDate}}
|
and created_at >= {{startDate}}
|
||||||
`,
|
`,
|
||||||
{ websiteId, startDate: subMinutes(new Date(), 5) },
|
{ websiteId, startDate },
|
||||||
);
|
);
|
||||||
|
|
||||||
return result[0] ?? null;
|
return result[0] ?? null;
|
||||||
|
|
@ -28,6 +29,7 @@ async function relationalQuery(websiteId: string) {
|
||||||
|
|
||||||
async function clickhouseQuery(websiteId: string): Promise<{ x: number }> {
|
async function clickhouseQuery(websiteId: string): Promise<{ x: number }> {
|
||||||
const { rawQuery } = clickhouse;
|
const { rawQuery } = clickhouse;
|
||||||
|
const startDate = subMinutes(new Date(), 5);
|
||||||
|
|
||||||
const result = await rawQuery(
|
const result = await rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -37,7 +39,7 @@ async function clickhouseQuery(websiteId: string): Promise<{ x: number }> {
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at >= {startDate:DateTime64}
|
and created_at >= {startDate:DateTime64}
|
||||||
`,
|
`,
|
||||||
{ websiteId, startDate: subMinutes(new Date(), 5) },
|
{ websiteId, startDate },
|
||||||
);
|
);
|
||||||
|
|
||||||
return result[0] ?? null;
|
return result[0] ?? null;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import {
|
import {
|
||||||
DATE_RANGE_CONFIG,
|
DATE_RANGE_CONFIG,
|
||||||
DEFAULT_DATE_RANGE,
|
DEFAULT_DATE_RANGE_VALUE,
|
||||||
DEFAULT_LOCALE,
|
DEFAULT_LOCALE,
|
||||||
DEFAULT_THEME,
|
DEFAULT_THEME,
|
||||||
LOCALE_CONFIG,
|
LOCALE_CONFIG,
|
||||||
|
|
@ -23,7 +23,7 @@ const initialState = {
|
||||||
locale: getItem(LOCALE_CONFIG) || DEFAULT_LOCALE,
|
locale: getItem(LOCALE_CONFIG) || DEFAULT_LOCALE,
|
||||||
theme: getItem(THEME_CONFIG) || getDefaultTheme() || DEFAULT_THEME,
|
theme: getItem(THEME_CONFIG) || getDefaultTheme() || DEFAULT_THEME,
|
||||||
timezone: getItem(TIMEZONE_CONFIG) || getTimezone(),
|
timezone: getItem(TIMEZONE_CONFIG) || getTimezone(),
|
||||||
dateRange: getItem(DATE_RANGE_CONFIG) || DEFAULT_DATE_RANGE,
|
dateRangeValue: getItem(DATE_RANGE_CONFIG) || DEFAULT_DATE_RANGE_VALUE,
|
||||||
shareToken: null,
|
shareToken: null,
|
||||||
user: null,
|
user: null,
|
||||||
config: null,
|
config: null,
|
||||||
|
|
@ -51,8 +51,8 @@ export function setConfig(config: object) {
|
||||||
store.setState({ config });
|
store.setState({ config });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setDateRange(dateRange: string | object) {
|
export function setDateRangeValue(dateRangeValue: string) {
|
||||||
store.setState({ dateRange });
|
store.setState({ dateRangeValue });
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useApp = store;
|
export const useApp = store;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue