Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wip] feat: allow time unit selection #3013

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
8 changes: 8 additions & 0 deletions src/app/(main)/profile/TimeUnitSettings.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.dropdown {
width: 200px;
}

div.menu {
max-height: 300px;
width: 300px;
}
29 changes: 29 additions & 0 deletions src/app/(main)/profile/TimeUnitSettings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useDateRange, useMessages } from 'components/hooks';
import useTimeUnit from 'components/hooks/useTimeUnit';
import { Button, Dropdown, Flexbox, Item } from 'react-basics';
import styles from './TimeUnitSettings.module.css';

export function TimeUnitSettings() {
const { formatMessage, labels } = useMessages();
const { dateRange } = useDateRange();
const { timeUnit, timeUnitOptions, updateTimeUnit } = useTimeUnit(dateRange);

const handleReset = () => updateTimeUnit('day');

return (
<Flexbox gap={10}>
<Dropdown
className={styles.dropdown}
items={timeUnitOptions}
value={timeUnit}
onChange={(value: any) => updateTimeUnit(value)}
menuProps={{ className: styles.menu }}
>
{item => <Item key={item}>{item}</Item>}
</Dropdown>
<Button onClick={handleReset}>{formatMessage(labels.reset)}</Button>
</Flexbox>
);
}

export default TimeUnitSettings;
14 changes: 8 additions & 6 deletions src/app/(main)/websites/[websiteId]/WebsiteChart.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useMemo } from 'react';
import PageviewsChart from 'components/metrics/PageviewsChart';
import useWebsitePageviews from 'components/hooks/queries/useWebsitePageviews';
import { useDateRange } from 'components/hooks';
import useWebsitePageviews from 'components/hooks/queries/useWebsitePageviews';
import useTimeUnit from 'components/hooks/useTimeUnit';
import PageviewsChart from 'components/metrics/PageviewsChart';
import { useMemo } from 'react';

export function WebsiteChart({
websiteId,
Expand All @@ -11,7 +12,8 @@ export function WebsiteChart({
compareMode?: boolean;
}) {
const { dateRange, dateCompare } = useDateRange(websiteId);
const { startDate, endDate, unit } = dateRange;
const { timeUnit } = useTimeUnit();
const { startDate, endDate } = dateRange;
const { data, isLoading } = useWebsitePageviews(websiteId, compareMode ? dateCompare : undefined);
const { pageviews, sessions, compare } = (data || {}) as any;

Expand Down Expand Up @@ -40,14 +42,14 @@ export function WebsiteChart({
return result;
}
return { pageviews: [], sessions: [] };
}, [data, startDate, endDate, unit]);
}, [data, startDate, endDate, timeUnit]);

return (
<PageviewsChart
data={chartData}
minDate={startDate.toISOString()}
maxDate={endDate.toISOString()}
unit={unit}
unit={timeUnit}
isLoading={isLoading}
/>
);
Expand Down
49 changes: 49 additions & 0 deletions src/components/hooks/useTimeUnit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { DEFAULT_TIME_UNIT, MAX_CHART_POINTS, TIME_UNIT_CONFIG, UNIT_TYPES } from 'lib/constants';
import { getDateLength } from 'lib/date';
import { DateRange, TimeUnit } from 'lib/types';
import { getItem, setItem } from 'next-basics';
import { useState } from 'react';
import useStore, { setTimeUnit } from 'store/app';

const selector = (state: { timeUnit: string }) => state.timeUnit;

export function useTimeUnit(dateRange?: DateRange) {
const storeTimeUnit = useStore(selector) || getItem(TIME_UNIT_CONFIG) || DEFAULT_TIME_UNIT;
const validTimeUnits = getValidTimeUnits(dateRange);
const [tempTimeUnit, setTempTimeUnit] = useState<TimeUnit>(() => {
return validTimeUnits.includes(storeTimeUnit) ? storeTimeUnit : validTimeUnits[0];
});

function getValidTimeUnits(dateRange: DateRange) {
if (!dateRange?.startDate || !dateRange?.endDate) {
return UNIT_TYPES;
}

return UNIT_TYPES.filter(unit => {
const points = getDateLength(dateRange.startDate, dateRange.endDate, unit);
return points <= MAX_CHART_POINTS;
});
}

function updateTimeUnit(value: TimeUnit) {
if (validTimeUnits.includes(value)) {
setTempTimeUnit(value);
}
}

function saveTimeUnit() {
if (validTimeUnits.includes(tempTimeUnit)) {
setTimeUnit(tempTimeUnit);
setItem(TIME_UNIT_CONFIG, tempTimeUnit);
}
}

return {
timeUnit: tempTimeUnit,
timeUnitOptions: validTimeUnits,
updateTimeUnit,
saveTimeUnit,
};
}

export default useTimeUnit;
26 changes: 26 additions & 0 deletions src/components/input/WebsiteChartSettings.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.container {
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 20px;
min-height: 100px;
}

.buttons {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
margin-top: 20px;
}

.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;
}
49 changes: 49 additions & 0 deletions src/components/input/WebsiteChartSettings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import TimeUnitSettings from 'app/(main)/profile/TimeUnitSettings';
import { useDateRange, useMessages } from 'components/hooks';
import useTimeUnit from 'components/hooks/useTimeUnit';
import { Button, Form, FormRow, Modal } from 'react-basics';
import styles from './WebsiteChartSettings.module.css';

export interface WebsiteChartSettingsProps {
websiteId: string;
isOpened?: boolean;
onClose: () => void;
onChange?: (value: string) => void;
}

export function WebsiteChartSettings({ websiteId, onClose }: WebsiteChartSettingsProps) {
const { formatMessage, labels } = useMessages();
const { dateRange } = useDateRange(websiteId);
const { saveTimeUnit } = useTimeUnit(dateRange);

const handleSave = () => {
saveTimeUnit();
onClose();
};

const handleClose = () => onClose();

return (
<>
<Modal onClose={handleClose}>
<div className={styles.container}>
<div>
<Form>
<FormRow label={formatMessage(labels.timeUnit)}>
<TimeUnitSettings />
</FormRow>
</Form>
</div>
<div className={styles.buttons}>
<Button variant="primary" onClick={handleSave} disabled={false}>
{formatMessage(labels.save)}
</Button>
<Button onClick={onClose}>{formatMessage(labels.cancel)}</Button>
</div>
</div>
</Modal>
</>
);
}

export default WebsiteChartSettings;
18 changes: 17 additions & 1 deletion src/components/input/WebsiteDateFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { useDateRange, useLocale } from 'components/hooks';
import { isAfter } from 'date-fns';
import { getOffsetDateRange } from 'lib/date';
import { DateRange } from 'lib/types';
import { useState } from 'react';
import { Button, Icon, Icons } from 'react-basics';
import DateFilter from './DateFilter';
import WebsiteChartSettings from './WebsiteChartSettings';
import styles from './WebsiteDateFilter.module.css';
import { DateRange } from 'lib/types';

export function WebsiteDateFilter({
websiteId,
Expand All @@ -16,6 +18,7 @@ export function WebsiteDateFilter({
const { dir } = useLocale();
const { dateRange, saveDateRange } = useDateRange(websiteId);
const { value, startDate, endDate, offset } = dateRange;
const [showChartParams, setShowChartParams] = useState(false);
const disableForward =
value === 'all' || isAfter(getOffsetDateRange(dateRange, 1).startDate, new Date());

Expand Down Expand Up @@ -52,6 +55,19 @@ export function WebsiteDateFilter({
</Button>
</div>
)}
<Button onClick={() => setShowChartParams(true)}>
<Icon rotate={dir === 'rtl' ? 270 : 90}>
<Icons.Menu />
</Icon>
</Button>
{showChartParams && (
<WebsiteChartSettings
websiteId={websiteId}
isOpened={showChartParams}
onClose={() => setShowChartParams(false)}
onChange={handleChange}
/>
)}
</div>
);
}
Expand Down
1 change: 1 addition & 0 deletions src/components/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export const labels = defineMessages({
confirmPassword: { id: 'label.confirm-password', defaultMessage: 'Confirm password' },
timezone: { id: 'label.timezone', defaultMessage: 'Timezone' },
defaultDateRange: { id: 'label.default-date-range', defaultMessage: 'Default date range' },
timeUnit: { id: 'label.time-unit', defaultMessage: 'Time Unit' },
language: { id: 'label.language', defaultMessage: 'Language' },
theme: { id: 'label.theme', defaultMessage: 'Theme' },
profile: { id: 'label.profile', defaultMessage: 'Profile' },
Expand Down
3 changes: 3 additions & 0 deletions src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const AUTH_TOKEN = 'umami.auth';
export const LOCALE_CONFIG = 'umami.locale';
export const TIMEZONE_CONFIG = 'umami.timezone';
export const DATE_RANGE_CONFIG = 'umami.date-range';
export const TIME_UNIT_CONFIG = 'umami.time-unit';
export const THEME_CONFIG = 'umami.theme';
export const DASHBOARD_CONFIG = 'umami.dashboard';
export const VERSION_CHECK = 'umami.version-check';
Expand All @@ -17,13 +18,15 @@ export const DEFAULT_LOCALE = process.env.defaultLocale || 'en-US';
export const DEFAULT_THEME = 'light';
export const DEFAULT_ANIMATION_DURATION = 300;
export const DEFAULT_DATE_RANGE = '24hour';
export const DEFAULT_TIME_UNIT = 'day';
export const DEFAULT_WEBSITE_LIMIT = 10;
export const DEFAULT_RESET_DATE = '2000-01-01';
export const DEFAULT_PAGE_SIZE = 10;
export const DEFAULT_DATE_COMPARE = 'prev';

export const REALTIME_RANGE = 30;
export const REALTIME_INTERVAL = 10000;
export const MAX_CHART_POINTS = 720;

export const FILTER_COMBINED = 'filter-combined';
export const FILTER_RAW = 'filter-raw';
Expand Down
11 changes: 9 additions & 2 deletions src/store/app.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { create } from 'zustand';
import {
DATE_RANGE_CONFIG,
DEFAULT_DATE_RANGE,
DEFAULT_LOCALE,
DEFAULT_THEME,
DEFAULT_TIME_UNIT,
LOCALE_CONFIG,
THEME_CONFIG,
TIME_UNIT_CONFIG,
TIMEZONE_CONFIG,
} from 'lib/constants';
import { getItem } from 'next-basics';
import { getTimezone } from 'lib/date';
import { getItem } from 'next-basics';
import { create } from 'zustand';

function getDefaultTheme() {
return typeof window !== 'undefined'
Expand All @@ -24,6 +26,7 @@ const initialState = {
theme: getItem(THEME_CONFIG) || getDefaultTheme() || DEFAULT_THEME,
timezone: getItem(TIMEZONE_CONFIG) || getTimezone(),
dateRange: getItem(DATE_RANGE_CONFIG) || DEFAULT_DATE_RANGE,
timeUnit: getItem(TIME_UNIT_CONFIG) || DEFAULT_TIME_UNIT,
shareToken: null,
user: null,
config: null,
Expand Down Expand Up @@ -59,4 +62,8 @@ export function setDateRange(dateRange: string | object) {
store.setState({ dateRange });
}

export function setTimeUnit(timeUnit: string | object) {
store.setState({ timeUnit });
}

export default store;