From 736794c288eb481637a4011c27bf20f29b04fa36 Mon Sep 17 00:00:00 2001 From: James Okada Date: Mon, 3 Jun 2024 13:34:06 -0700 Subject: [PATCH 1/2] frontend for filtering by date range --- .../analytics/analytics.component.tsx | 22 ++- .../analytics/stars.table.component.tsx | 29 +++- .../components/launches/helpers/date.query.ts | 39 ++++++ .../launches/helpers/date.range.picker.tsx | 129 ++++++++++++++++++ 4 files changed, 214 insertions(+), 5 deletions(-) create mode 100644 apps/frontend/src/components/launches/helpers/date.query.ts create mode 100644 apps/frontend/src/components/launches/helpers/date.range.picker.tsx diff --git a/apps/frontend/src/components/analytics/analytics.component.tsx b/apps/frontend/src/components/analytics/analytics.component.tsx index 17e9435a..17364b49 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 92fc0836..64694268 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'); @@ -37,9 +39,16 @@ export const UpDown: FC<{ name: string; param: string }> = (props) => { (newState: string) => { const query = newState === 'none' ? `` : `?key=${param}&state=${newState}`; - router.replace(`/analytics${query}`); + let dateRange = ''; + if (startDate) { + dateRange = `&startDate=${startDate}`; + } + if (endDate) { + dateRange = `${dateRange}&endDate=${endDate}`; + } + router.replace(`/analytics${query}${dateRange}`); }, - [state, param] + [state, param, startDate, endDate] ); const changeState = useCallback(() => { @@ -96,6 +105,10 @@ export const StarsTableComponent = () => { 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(); @@ -150,9 +163,17 @@ export const StarsTableComponent = () => { (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}`); + + let dateRange = ''; + if (startDate) { + dateRange = `&startDate=${startDate}`; + } + if (endDate) { + dateRange = `${dateRange}&endDate=${endDate}`; + } + router.replace(`/analytics?page=${newPage}${keyAndState}${dateRange}`); }, - [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 00000000..c75dc19c --- /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 00000000..eaad539a --- /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', + }, + }, + }} + /> +
+ )} +
+ ); +}; From 5f4a46b3d526679d6aeb7952ed40098fcc5e6458 Mon Sep 17 00:00:00 2001 From: James Okada Date: Mon, 3 Jun 2024 15:24:31 -0700 Subject: [PATCH 2/2] change to use urlsearchparams --- .../analytics/stars.table.component.tsx | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/apps/frontend/src/components/analytics/stars.table.component.tsx b/apps/frontend/src/components/analytics/stars.table.component.tsx index 64694268..bb525f1c 100644 --- a/apps/frontend/src/components/analytics/stars.table.component.tsx +++ b/apps/frontend/src/components/analytics/stars.table.component.tsx @@ -37,16 +37,19 @@ export const UpDown: FC<{ name: string; param: string }> = (props) => { const changeStateUrl = useCallback( (newState: string) => { - const query = - newState === 'none' ? `` : `?key=${param}&state=${newState}`; - let dateRange = ''; + const params = new URLSearchParams(); if (startDate) { - dateRange = `&startDate=${startDate}`; + params.set('startDate', startDate); } if (endDate) { - dateRange = `${dateRange}&endDate=${endDate}`; + params.set('endDate', endDate); } - router.replace(`/analytics${query}${dateRange}`); + + if (newState !== 'none') { + params.set('key', param); + params.set('state', newState); + } + router.replace(`/analytics?${params.toString()}`); }, [state, param, startDate, endDate] ); @@ -55,7 +58,7 @@ export const UpDown: FC<{ name: string; param: string }> = (props) => { changeStateUrl( state === 'none' ? 'desc' : state === 'desc' ? 'asc' : 'none' ); - }, [state, param]); + }, [state, param, startDate, endDate]); return (
{ const changePage = useCallback( (type: 'increase' | 'decrease') => () => { - const newPage = type === 'increase' ? page + 1 : page - 1; - const keyAndState = key && state ? `&key=${key}&state=${state}` : ''; + 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); + } - let dateRange = ''; if (startDate) { - dateRange = `&startDate=${startDate}`; + params.set('startDate', startDate); } if (endDate) { - dateRange = `${dateRange}&endDate=${endDate}`; + params.set('endDate', endDate); } - router.replace(`/analytics?page=${newPage}${keyAndState}${dateRange}`); + router.replace(`/analytics?${params.toString()}`); }, [page, key, state, startDate, endDate] );