Updated date range handling.

This commit is contained in:
Mike Cao 2025-06-25 14:27:17 -07:00
parent 6d1603fa28
commit 5ca51b3e8f
19 changed files with 101 additions and 99 deletions

View file

@ -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
View file

@ -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

View file

@ -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>
); );

View file

@ -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">

View file

@ -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}

View file

@ -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} />

View file

@ -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;
} }

View file

@ -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>
); );
} }

View file

@ -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>
); );

View file

@ -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);
} }
}; };

View file

@ -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

View file

@ -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>

View file

@ -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({

View file

@ -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>
); );

View file

@ -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';

View file

@ -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;

View file

@ -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,
}; };
} }
} }

View file

@ -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;

View file

@ -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;