diff --git a/apps/frontend/src/components/analytics/analytics.component.tsx b/apps/frontend/src/components/analytics/analytics.component.tsx
index 932bd4aca..b867747c9 100644
--- a/apps/frontend/src/components/analytics/analytics.component.tsx
+++ b/apps/frontend/src/components/analytics/analytics.component.tsx
@@ -6,10 +6,22 @@ import { StarsTableComponent } from '@gitroom/frontend/components/analytics/star
import useSWR from 'swr';
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
import { LoadingComponent } from '@gitroom/frontend/components/layout/loading';
+import { usePathname, useRouter, useSearchParams } from 'next/navigation';
+import {
+ DateRange,
+ DateRangePicker,
+} from '../launches/helpers/date.range.picker';
+import {
+ getDateRangeQuery,
+ getDateRangeUrl,
+} from '../launches/helpers/date.query';
export const AnalyticsComponent: FC = () => {
const fetch = useFetch();
-
+ const router = useRouter();
+ const searchParams = useSearchParams();
+ const pathname = usePathname();
+ const dateRange: DateRange = getDateRangeQuery(searchParams);
const load = useCallback(async (path: string) => {
return await (await fetch(path)).json();
}, []);
@@ -27,9 +39,17 @@ export const AnalyticsComponent: FC = () => {
return ;
}
+ const changeDateRange = (newDateRange: DateRange) => {
+ const dateRangeUrl = getDateRangeUrl(searchParams, newDateRange);
+ router.replace(`${pathname}?${dateRangeUrl}`);
+ };
+
return (
diff --git a/apps/frontend/src/components/analytics/stars.table.component.tsx b/apps/frontend/src/components/analytics/stars.table.component.tsx
index 7ea1a70ac..bf8c66597 100644
--- a/apps/frontend/src/components/analytics/stars.table.component.tsx
+++ b/apps/frontend/src/components/analytics/stars.table.component.tsx
@@ -21,6 +21,8 @@ export const UpDown: FC<{ name: string; param: string }> = (props) => {
const { name, param } = props;
const router = useRouter();
const searchParams = useSearchParams();
+ const startDate = searchParams.get('startDate');
+ const endDate = searchParams.get('endDate');
const state = useMemo(() => {
const newName = searchParams.get('key');
@@ -35,18 +37,28 @@ export const UpDown: FC<{ name: string; param: string }> = (props) => {
const changeStateUrl = useCallback(
(newState: string) => {
- const query =
- newState === 'none' ? `` : `?key=${param}&state=${newState}`;
- router.replace(`/analytics${query}`);
+ const params = new URLSearchParams();
+ if (startDate) {
+ params.set('startDate', startDate);
+ }
+ if (endDate) {
+ params.set('endDate', endDate);
+ }
+
+ if (newState !== 'none') {
+ params.set('key', param);
+ params.set('state', newState);
+ }
+ router.replace(`/analytics?${params.toString()}`);
},
- [state, param]
+ [state, param, startDate, endDate]
);
const changeState = useCallback(() => {
changeStateUrl(
state === 'none' ? 'desc' : state === 'desc' ? 'asc' : 'none'
);
- }, [state, param]);
+ }, [state, param, startDate, endDate]);
return (
{
const page = +(searchParams.get('page') || 1);
const key = searchParams.get('key');
const state = searchParams.get('state');
+
+ const startDate = searchParams.get('startDate');
+ const endDate = searchParams.get('endDate');
+
const [loading, setLoading] = useState(false);
const [, startTransition] = useTransition();
@@ -148,11 +164,23 @@ export const StarsTableComponent = () => {
const changePage = useCallback(
(type: 'increase' | 'decrease') => () => {
- const newPage = type === 'increase' ? page + 1 : page - 1;
- const keyAndState = key && state ? `&key=${key}&state=${state}` : '';
- router.replace(`/analytics?page=${newPage}${keyAndState}`);
+ const params = new URLSearchParams();
+ const pageValue = type === 'increase' ? page + 1 : page - 1;
+ params.set('page', pageValue.toString());
+ if (key && state) {
+ params.set('key', key);
+ params.set('state', state);
+ }
+
+ if (startDate) {
+ params.set('startDate', startDate);
+ }
+ if (endDate) {
+ params.set('endDate', endDate);
+ }
+ router.replace(`/analytics?${params.toString()}`);
},
- [page, key, state]
+ [page, key, state, startDate, endDate]
);
return (
diff --git a/apps/frontend/src/components/launches/helpers/date.query.ts b/apps/frontend/src/components/launches/helpers/date.query.ts
new file mode 100644
index 000000000..c75dc19cf
--- /dev/null
+++ b/apps/frontend/src/components/launches/helpers/date.query.ts
@@ -0,0 +1,39 @@
+import dayjs from 'dayjs';
+import { ReadonlyURLSearchParams } from 'next/navigation';
+import { DateRange } from './date.range.picker';
+import customParseFormat from 'dayjs/plugin/customParseFormat';
+
+const DATE_QUERY_FORMAT = 'YYYY-MM-DD';
+
+export const getDateRangeQuery = (searchParams: ReadonlyURLSearchParams) => {
+ const startDateParams = searchParams.get('startDate');
+ const endDateParams = searchParams.get('endDate');
+ dayjs.extend(customParseFormat);
+ const dateRange: DateRange = {
+ startDate: startDateParams
+ ? dayjs(startDateParams, DATE_QUERY_FORMAT)
+ : undefined,
+ endDate: endDateParams
+ ? dayjs(endDateParams, DATE_QUERY_FORMAT)
+ : undefined,
+ };
+ return dateRange;
+};
+export const getDateRangeUrl = (
+ searchParams: ReadonlyURLSearchParams,
+ dateRange: DateRange
+) => {
+ const params = new URLSearchParams(searchParams);
+ if (dateRange.startDate) {
+ params.set('startDate', dateRange.startDate.format(DATE_QUERY_FORMAT));
+ } else {
+ params.delete('startDate');
+ }
+
+ if (dateRange.endDate) {
+ params.set('endDate', dateRange.endDate.format(DATE_QUERY_FORMAT));
+ } else {
+ params.delete('endDate');
+ }
+ return params.toString();
+};
diff --git a/apps/frontend/src/components/launches/helpers/date.range.picker.tsx b/apps/frontend/src/components/launches/helpers/date.range.picker.tsx
new file mode 100644
index 000000000..eaad539a6
--- /dev/null
+++ b/apps/frontend/src/components/launches/helpers/date.range.picker.tsx
@@ -0,0 +1,129 @@
+import { FC, useCallback, useState } from 'react';
+import dayjs from 'dayjs';
+import { Calendar, RangeCalendar, TimeInput } from '@mantine/dates';
+import { useClickOutside } from '@mantine/hooks';
+import { Button } from '@gitroom/react/form/button';
+
+export type DateRange = {
+ startDate?: dayjs.Dayjs;
+ endDate?: dayjs.Dayjs;
+};
+
+export const DateRangePicker: FC<{
+ dateRange: DateRange;
+ onChange: (dateRange: DateRange) => void;
+}> = (props) => {
+ const { dateRange, onChange } = props;
+ const [open, setOpen] = useState(false);
+ const startDate: Date | null = dateRange.startDate
+ ? dateRange.startDate.toDate()
+ : null;
+ const endDate: Date | null = dateRange.endDate
+ ? dateRange.endDate.toDate()
+ : null;
+
+ const rangeCalendarValue: [Date | null, Date | null] = [startDate, endDate];
+
+ const isSameDay = dateRange?.startDate?.isSame(dateRange?.endDate);
+ const changeShow = useCallback(() => {
+ setOpen((prev) => !prev);
+ }, []);
+
+ const ref = useClickOutside
(() => {
+ setOpen(false);
+ });
+
+ const dateDisplay = () => {
+ if (isSameDay) {
+ return dateRange?.startDate?.format('DD MMM YYYY');
+ }
+ return `${dateRange?.startDate?.format(
+ 'DD MMM YYYY'
+ )} - ${dateRange?.endDate?.format('DD MMM YYYY')}`;
+ };
+
+ const changeDateRange = useCallback(
+ (newDateRange: [Date | null, Date | null]) => {
+ if (newDateRange[0] && newDateRange[1]) {
+ const result = {
+ startDate: dayjs(newDateRange[0]),
+ endDate: dayjs(newDateRange[1]),
+ };
+ onChange(result);
+ }
+ },
+ [onChange]
+ );
+
+ return (
+
+ {dateRange?.startDate && dateRange?.endDate ? (
+ <>
+
{dateDisplay()}
+
+ >
+ ) : (
+
+ )}
+ {open && (
+
e.stopPropagation()}
+ className="animate-normalFadeDown absolute top-[100%] mt-[16px] right-0 bg-sixth border border-tableBorder text-white rounded-[16px] z-[300] p-[16px] flex flex-col"
+ >
+ {
+ if (modifiers.selectedInRange) {
+ return '!text-white !bg-seventh !outline-none';
+ }
+ if (modifiers.inRange) {
+ return '!bg-seventh !text-white';
+ }
+ if (modifiers.weekend) {
+ return '!text-[#B69DEC]';
+ }
+
+ if (modifiers.outside) {
+ return '!text-gray';
+ }
+
+ return '!text-white';
+ }}
+ classNames={{
+ day: 'hover:bg-seventh',
+ calendarHeaderControl: 'text-white hover:bg-third',
+ calendarHeaderLevel: 'text-white hover:bg-third', // cell: 'child:!text-white'
+ }}
+ styles={{
+ // kind of a hack to make sure that we are able to style when a range is selected but not finished
+ day: {
+ '&[data-in-range], &[data-in-range]:hover': {
+ backgroundColor: `rgb(114, 54, 241)`,
+ color: 'white',
+ },
+ },
+ }}
+ />
+
+ )}
+
+ );
+};