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

Implemented Date Range Input. #2328

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/backoffice-v2/src/Router/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { NotFoundRedirect } from '@/pages/NotFound/NotFound';
import { TransactionMonitoringAlerts } from '@/pages/TransactionMonitoringAlerts/TransactionMonitoringAlerts.page';
import { TransactionMonitoring } from '@/pages/TransactionMonitoring/TransactionMonitoring';
import { TransactionMonitoringAlertsAnalysisPage } from '@/pages/TransactionMonitoringAlertsAnalysis/TransactionMonitoringAlertsAnalysis.page';
import { Home } from '../common/components/atoms/Home/Home';
import { Home } from '@/pages/Home/Home.page';
import { Statistics } from '@/pages/Statistics/Statistics.page';
import { Workflows } from '@/pages/Workflows/Workflows.page';

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React, { useState, ComponentProps } from 'react';
import { CalendarIcon } from '@radix-ui/react-icons';
import { formatDate } from '@/common/utils/format-date';
import { DateRange } from 'react-day-picker';
import dayjs from 'dayjs';
import { ctw } from '@/common/utils/ctw/ctw';
import { Button } from '../../atoms/Button/Button';
import { Calendar } from '../../organisms/Calendar/Calendar';
import { Popover, PopoverContent, PopoverTrigger } from '@ballerine/ui';

type TDateRangePickerProps = {
onChange: ComponentProps<typeof Calendar>['onSelect'];
value: ComponentProps<typeof Calendar>['value'];
className: ComponentProps<'div'>['className'];
};

export function DateRangePicker({ onChange, value, className }: TDateRangePickerProps) {
const currentDate = new Date();
const [date, setDate] = useState<DateRange | undefined>({
from: new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate()),
to: dayjs(new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate(), 1))
.add(1, 'month')
.toDate(),
});

const handleDateChange = (selection: DateRange | undefined) => {
setDate(selection);
onChange(selection);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was expecting these lines to be removed and for the component to receive onChange and value as props. 🙂

Suggested change
const currentDate = new Date();
const [date, setDate] = useState<DateRange | undefined>({
from: new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate()),
to: dayjs(new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate(), 1))
.add(1, 'month')
.toDate(),
});
const handleDateChange = (selection: DateRange | undefined) => {
setDate(selection);
onChange(selection);
};


return (
<div className={ctw('grid gap-2', className)}>
<Popover>
<PopoverTrigger asChild>
<Button
id="date"
variant={'outline'}
className={ctw('w-[300px] justify-start text-left font-normal', {
'text-muted-foreground': !date,
})}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{date?.from && date?.to && (
<>
{formatDate(date.from, 'LLL dd, y')} - {formatDate(date.to, 'LLL dd, y')}
</>
)}
{date?.from && !date?.to && formatDate(date.from, 'LLL dd, y')}
{!date?.from && !date?.to && <span>Pick a date</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
initialFocus
mode="range"
selected={date}
onSelect={handleDateChange}
numberOfMonths={2}
/>
</PopoverContent>
</Popover>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import * as React from 'react';
import { ChevronLeftIcon, ChevronRightIcon } from '@radix-ui/react-icons';
import { ctw } from '@/common/utils/ctw/ctw';
import { DayPicker } from 'react-day-picker';
import { buttonVariants } from '../../atoms/Button/Button';

export type CalendarProps = React.ComponentProps<typeof DayPicker>;
export function Calendar({
className,
classNames,
showOutsideDays = true,
...props
}: CalendarProps) {
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={ctw('p-3', className)}
classNames={{
months: 'flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0',
month: 'space-y-4',
caption: 'flex justify-center pt-1 relative items-center',
caption_label: 'text-sm font-medium',
nav: 'space-x-1 flex items-center',
nav_button: ctw(
buttonVariants({ variant: 'outline' }),
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
),
nav_button_previous: 'absolute left-1',
nav_button_next: 'absolute right-1',
table: 'w-full border-collapse space-y-1',
head_row: 'flex',
head_cell: 'text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]',
row: 'flex w-full mt-2',
cell: ctw(
'relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected].day-range-end)]:rounded-r-md',
props.mode === 'range'
? '[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md'
: '[&:has([aria-selected])]:rounded-md',
),
day: ctw(
buttonVariants({ variant: 'ghost' }),
'h-8 w-8 p-0 font-normal aria-selected:opacity-100',
),
day_range_start: 'day-range-start',
day_range_end: 'day-range-end',
day_selected:
'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground',
day_today: 'bg-accent text-accent-foreground',
day_outside:
'day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30',
day_disabled: 'text-muted-foreground opacity-50',
day_range_middle: 'aria-selected:bg-accent aria-selected:text-accent-foreground',
day_hidden: 'invisible',
...classNames,
}}
components={{
IconLeft: ({ ...props }) => <ChevronLeftIcon className="h-4 w-4" />,
IconRight: ({ ...props }) => <ChevronRightIcon className="h-4 w-4" />,
}}
{...props}
/>
);
}
Calendar.displayName = 'Calendar';
24 changes: 24 additions & 0 deletions apps/backoffice-v2/src/common/hooks/useHomeLogic/useHomeLogic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ComponentProps } from 'react';
import { DateRangePicker } from '@/common/components/molecules/DateRangePicker/DateRangePicker';
import { useZodSearchParams } from '@/common/hooks/useZodSearchParams/useZodSearchParams';
import { HomeSearchSchema } from '@/pages/Home/home-search-schema';

export const useHomeLogic = () => {
const [searchParams, setSearchParams] = useZodSearchParams(HomeSearchSchema);

const handleDateRangeChange: ComponentProps<typeof DateRangePicker>['onChange'] = (range: {
start: { toISOString: () => string };
end: { toISOString: () => string };
}) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ComponentProps['onChange'] gives you the type for range. If the type is wrong then we're doing something incorrectly.

Suggested change
const handleDateRangeChange: ComponentProps<typeof DateRangePicker>['onChange'] = (range: {
start: { toISOString: () => string };
end: { toISOString: () => string };
}) => {
const handleDateRangeChange: ComponentProps<typeof DateRangePicker>['onChange'] = (range) => {

const from = range?.start?.toISOString() || '';
const to = range?.end?.toISOString() || '';

setSearchParams({ from, to });
};

return {
from: searchParams.from || '',
to: searchParams.to || '',
handleDateRangeChange,
};
};
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import React, { FunctionComponent, useEffect } from 'react';
import { NavLink, Outlet, useLocation, useNavigate } from 'react-router-dom';
import { t } from 'i18next';
import { useAuthenticatedUserQuery } from '../../../../domains/auth/hooks/queries/useAuthenticatedUserQuery/useAuthenticatedUserQuery';
import { UserAvatar } from '../UserAvatar/UserAvatar';
import { useAuthenticatedUserQuery } from '@/domains/auth/hooks/queries/useAuthenticatedUserQuery/useAuthenticatedUserQuery';
import { UserAvatar } from '@/common/components/atoms/UserAvatar/UserAvatar';
import { Tabs } from '@/common/components/organisms/Tabs/Tabs';
import { TabsList } from '@/common/components/organisms/Tabs/Tabs.List';
import { TabsTrigger } from '@/common/components/organisms/Tabs/Tabs.Trigger';
import { TabsContent } from '@/common/components/organisms/Tabs/Tabs.Content';
import { useLocale } from '@/common/hooks/useLocale/useLocale';
import { useZodSearchParams } from '@/common/hooks/useZodSearchParams/useZodSearchParams';
import { HomeSearchSchema } from './home-search-schema';
import { DateRangePicker } from '@/common/components/molecules/DateRangePicker/DateRangePicker';
import { useHomeLogic } from '@/common/hooks/useHomeLogic/useHomeLogic';

export const Home: FunctionComponent = () => {
const { data: session } = useAuthenticatedUserQuery();
const { firstName, fullName, avatarUrl } = session?.user || {};
const locale = useLocale();
const { pathname } = useLocation();
const navigate = useNavigate();
const [dateRange, setDateRange] = useZodSearchParams(HomeSearchSchema);
const { handleDateRangeChange, from, to } = useHomeLogic();

useEffect(() => {
if (pathname !== `/${locale}` && pathname !== `/${locale}/home`) {
Expand All @@ -29,16 +29,26 @@ export const Home: FunctionComponent = () => {

return (
<div className={`flex flex-col gap-10 p-10`}>
<div className={`flex items-center`}>
<UserAvatar
fullName={fullName ?? ''}
className={`mr-2 d-6`}
avatarUrl={avatarUrl ?? undefined}
<div className={`flex items-center justify-between`}>
<div className={`flex items-center`}>
<UserAvatar
fullName={fullName ?? ''}
className={`mr-2 d-6`}
avatarUrl={avatarUrl ?? undefined}
/>
<h3 className={`flex max-w-[45ch] break-all text-2xl font-semibold`}>
{t(`home.greeting`)}
{firstName && ` ${firstName}`}
</h3>
</div>
<DateRangePicker
onChange={handleDateRangeChange}
value={{
start: from ? new Date(from) : null,
end: to ? new Date(to) : null,
}}
className={'<div>'}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like a mistake.

/>
<h3 className={`flex max-w-[45ch] break-all text-2xl font-semibold`}>
{t(`home.greeting`)}
{firstName && ` ${firstName}`}
</h3>
</div>
<div>
<Tabs defaultValue={pathname} key={pathname}>
Expand Down