Skip to content

Commit

Permalink
refactor: optimize date range picker component (#3592)
Browse files Browse the repository at this point in the history
* refactor: optimize date range picker component

- Extract PredefinedRanges into a separate component for better maintainability
- Implement createRangeHelper factory function to reduce code duplication
- Add memoization for predefined ranges to prevent unnecessary recalculations
- Improve type safety with DateRangeGetter type
- Standardize date comparison using startOfDay
- Enhance UI with consistent button styling and layout

Performance improvements:
- Reduce redundant date calculations
- Prevent unnecessary re-renders with useMemo
- Centralize date range creation logic

Technical improvements:
- Better type safety
- More maintainable code structure
- Consistent date handling

* deepscan

* deepscan

* deepscan

* fix: memoize createRange function to prevent unnecessary re-renders in date-range-picker

* deepscan

* fix: conflit
  • Loading branch information
Innocent-Akim authored Feb 7, 2025
1 parent 1c67350 commit 59ab502
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 125 deletions.
1 change: 1 addition & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
"deserunt",
"digitaloceanspaces",
"dimesions",
"Ipredefine",
"discrepenancy",
"dolor",
"dolore",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
import { DateRange } from 'react-day-picker';
import { useTranslations } from 'next-intl';
import { SettingsIcon } from './team-icon';
import { TranslationHooks } from 'next-intl';
import { CalendarIcon } from '@radix-ui/react-icons';

interface DateRangePickerProps {
Expand Down Expand Up @@ -51,100 +52,6 @@ export function DateRangePicker({ className, onDateRangeChange }: DateRangePicke
}
};

const predefinedRanges = [
{
label: t('common.TODAY'),
action: () => {
const today = new Date();
handleDateRangeChange({ from: today, to: today });
},
isSelected: (range: DateRange | undefined) => {
if (!range?.from || !range?.to) return false;
const today = new Date();
return isEqual(range.from, today) && isEqual(range.to, today);
}
},
{
label: t('common.YESTERDAY'),
action: () => {
const yesterday = subDays(new Date(), 1);
handleDateRangeChange({ from: yesterday, to: yesterday });
},
isSelected: (range: DateRange | undefined) => {
if (!range?.from || !range?.to) return false;
const yesterday = subDays(new Date(), 1);
return isEqual(range.from, yesterday) && isEqual(range.to, yesterday);
}
},
{
label: t('common.THIS_WEEK'),
action: () => {
const today = new Date();
handleDateRangeChange({
from: startOfWeek(today, { weekStartsOn: 1 }),
to: endOfWeek(today, { weekStartsOn: 1 })
});
},
isSelected: (range: DateRange | undefined) => {
if (!range?.from || !range?.to) return false;
const today = new Date();
const weekStart = startOfWeek(today, { weekStartsOn: 1 });
const weekEnd = endOfWeek(today, { weekStartsOn: 1 });
return isEqual(range.from, weekStart) && isEqual(range.to, weekEnd);
}
},
{
label: t('common.LAST_WEEK'),
action: () => {
const lastWeek = subWeeks(new Date(), 1);
handleDateRangeChange({
from: startOfWeek(lastWeek, { weekStartsOn: 1 }),
to: endOfWeek(lastWeek, { weekStartsOn: 1 })
});
},
isSelected: (range: DateRange | undefined) => {
if (!range?.from || !range?.to) return false;
const lastWeek = subWeeks(new Date(), 1);
const weekStart = startOfWeek(lastWeek, { weekStartsOn: 1 });
const weekEnd = endOfWeek(lastWeek, { weekStartsOn: 1 });
return isEqual(range.from, weekStart) && isEqual(range.to, weekEnd);
}
},
{
label: t('common.THIS_MONTH'),
action: () => {
const today = new Date();
handleDateRangeChange({
from: startOfMonth(today),
to: endOfMonth(today)
});
},
isSelected: (range: DateRange | undefined) => {
if (!range?.from || !range?.to) return false;
const today = new Date();
const monthStart = startOfMonth(today);
const monthEnd = endOfMonth(today);
return isEqual(range.from, monthStart) && isEqual(range.to, monthEnd);
}
},
{
label: t('common.LAST_MONTH'),
action: () => {
const lastMonth = subMonths(new Date(), 1);
handleDateRangeChange({
from: startOfMonth(lastMonth),
to: endOfMonth(lastMonth)
});
},
isSelected: (range: DateRange | undefined) => {
if (!range?.from || !range?.to) return false;
const lastMonth = subMonths(new Date(), 1);
const monthStart = startOfMonth(lastMonth);
const monthEnd = endOfMonth(lastMonth);
return isEqual(range.from, monthStart) && isEqual(range.to, monthEnd);
}
}
];

const formatDateRange = (range: DateRange) => {
if (!range.from) return 'Select date range';
Expand Down Expand Up @@ -219,23 +126,8 @@ export function DateRangePicker({ className, onDateRangeChange }: DateRangePicke
disabled={(date) => date >= startOfDay(new Date())}
/>
</div>
<div className="p-0.5 space-y-0.5 border-l max-w-32">
{predefinedRanges.map((range) => (
<Button
key={range.label}
variant={range.isSelected(dateRange) ? 'default' : 'ghost'}
className={cn(
'justify-start w-full text-sm font-normal dark:text-gray-100 h-8',
range.isSelected(dateRange) &&
'bg-primary text-primary-foreground hover:bg-primary/90'
)}
onClick={() => {
range.action();
}}
>
{range.label}
</Button>
))}
<div className="p-1 space-y-1 border-l max-w-36">
<PredefinedRanges handleDateRangeChange={handleDateRangeChange} t={t} dateRange={dateRange} />
</div>
</div>
<div className="flex gap-1 justify-end p-0.5 border-t">
Expand All @@ -262,3 +154,124 @@ export function DateRangePicker({ className, onDateRangeChange }: DateRangePicke
</Popover>
);
}
interface PredefinedRangeProps {
handleDateRangeChange: (range: DateRange | undefined) => void;
t: TranslationHooks;
dateRange: DateRange | undefined;
}

type DateRangeGetter = () => { from: Date; to: Date };

const createRangeHelper = (handleDateRangeChange: (range: DateRange | undefined) => void) => {
return (getRange: DateRangeGetter) => {
const range = getRange();
return {
action: () => {
const newRange = {
from: new Date(range.from),
to: new Date(range.to)
};
handleDateRangeChange(newRange);
},
isSelected: (currentRange: DateRange | undefined) => {
if (!currentRange?.from || !currentRange?.to) return false;
return isEqual(
startOfDay(currentRange.from),
startOfDay(range.from)
) && isEqual(
startOfDay(currentRange.to),
startOfDay(range.to)
);
}
};
};
};

const PredefinedRanges = ({ handleDateRangeChange, t, dateRange }: PredefinedRangeProps) => {
const weekOptions = { weekStartsOn: 1 as const };

const createRange = React.useMemo(
() => createRangeHelper(handleDateRangeChange),
[handleDateRangeChange]
);

const predefinedRanges = React.useMemo(
() => [
{
label: t('common.TODAY'),
...createRange(() => {
const today = new Date();
return { from: today, to: today };
})
},
{
label: t('common.YESTERDAY'),
...createRange(() => {
const yesterday = subDays(new Date(), 1);
return { from: yesterday, to: yesterday };
})
},
{
label: t('common.THIS_WEEK'),
...createRange(() => {
const today = new Date();
return {
from: startOfWeek(today, weekOptions),
to: endOfWeek(today, weekOptions)
};
})
},
{
label: t('common.LAST_WEEK'),
...createRange(() => {
const lastWeek = subWeeks(new Date(), 1);
return {
from: startOfWeek(lastWeek, weekOptions),
to: endOfWeek(lastWeek, weekOptions)
};
})
},
{
label: t('common.THIS_MONTH'),
...createRange(() => {
const today = new Date();
return {
from: startOfMonth(today),
to: endOfMonth(today)
};
})
},
{
label: t('common.LAST_MONTH'),
...createRange(() => {
const lastMonth = subMonths(new Date(), 1);
return {
from: startOfMonth(lastMonth),
to: endOfMonth(lastMonth)
};
})
}
],
[createRange, t]
);

return (
<div className='flex flex-col gap-2 p-2'>
{predefinedRanges.map((range) => (
<Button
key={range.label}
variant={range.isSelected(dateRange) ? 'default' : 'ghost'}
className={cn(
'justify-start w-full font-normal dark:text-gray-100 border rounded',
range.isSelected(dateRange) && 'bg-primary text-primary-foreground hover:bg-primary/90'
)}
onClick={() => {
range.action();
}}
>
{range.label}
</Button>
))}
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ function TimesheetDetailModal({ closeModal, isOpen, timesheet, timesheetDetailMo
showCloseIcon
className="bg-light--theme-light dark:bg-dark--theme-light p-5 rounded w-full md:w-40 md:min-w-[35rem]"
titleClass="font-bold flex justify-start w-full text-xl">
<div className=' py-4 w-full'>
<div className='py-4 w-full'>
<div className="flex flex-col w-full gap-4 h-[60vh] max-h-[60vh] overflow-y-auto ">
{(() => {
switch (timesheetDetailMode) {
Expand Down Expand Up @@ -89,13 +89,13 @@ const MembersWorkedCard = ({ element, t }: { element: TimesheetLog[], t: Transla
<AccordionTrigger
type="button"
className={cn(
'flex flex-row-reverse justify-end items-center w-full h-[50px] rounded-sm gap-x-2 hover:no-underline px-2',
'flex flex-row-reverse gap-x-2 justify-end items-center px-2 w-full rounded-sm h-[50px] hover:no-underline',

)}
>
<div className='flex justify-between items-center w-full'>
<div className='flex items-center gap-2'>
<EmployeeAvatar className='w-10 h-10 rounded-full shadow-md border' imageUrl={timesheet.element[0].employee.user?.imageUrl!} />
<div className='flex gap-2 items-center'>
<EmployeeAvatar className='w-10 h-10 rounded-full border shadow-md' imageUrl={timesheet.element[0].employee.user?.imageUrl??''} />
<span className='font-bold'>{timesheet.element[0].employee.fullName}</span>
</div>
<Badge
Expand All @@ -121,10 +121,10 @@ const MembersWorkedCard = ({ element, t }: { element: TimesheetLog[], t: Transla
'flex flex-row-reverse justify-end items-center w-full h-[50px] rounded-sm gap-x-2 hover:no-underline px-2',
statusColor(status).text
)}>
<div className="flex items-center justify-between w-full space-x-1">
<div className="flex justify-between items-center space-x-1 w-full">
<div className="flex items-center space-x-1">
<div className={cn('p-2 rounded', statusColor(status).bg)}></div>
<div className="flex items-center gap-x-1">
<div className="flex gap-x-1 items-center">
<span className="text-base font-normal text-gray-400 uppercase">
{status === 'DENIED' ? 'REJECTED' : status}
</span>
Expand All @@ -150,7 +150,7 @@ const MembersWorkedCard = ({ element, t }: { element: TimesheetLog[], t: Transla
borderBottomColor: statusColor(status).bg
}}
className={cn(
'flex items-center border-b border-b-gray-200 dark:border-b-gray-600 space-x-4 p-1 h-[60px]'
'flex items-center p-1 space-x-4 border-b border-b-gray-200 dark:border-b-gray-600 h-[60px]'
)} >
<div className="flex-[2]">
<TaskNameInfoDisplay
Expand All @@ -166,7 +166,7 @@ const MembersWorkedCard = ({ element, t }: { element: TimesheetLog[], t: Transla
taskNumberClassName="text-sm"
/>
</div>
<div className="flex items-center gap-2 flex-1">
<div className="flex flex-1 gap-2 items-center">
{items.project?.imageUrl && <ProjectLogo className='w-[28px] h-[28px] drop-shadow-[25%] rounded-[8px]' imageUrl={items.project.imageUrl} />}
<span className="font-medium">{items.project?.name}</span>
</div>
Expand Down Expand Up @@ -218,11 +218,11 @@ const MenHoursCard = ({ element, t }: MenHoursCardProps) => {
style={{ backgroundColor: statusColor(timesheet.element[0].timesheet.status).bgOpacity }}
type="button"
className={cn(
'flex flex-row-reverse justify-end items-center w-full h-[50px] rounded-sm gap-x-2 hover:no-underline px-2',
'flex flex-row-reverse gap-x-2 justify-end items-center px-2 w-full rounded-sm h-[50px] hover:no-underline',

)}>
<div className='flex justify-between items-center w-full'>
<div className='flex items-center gap-2'>
<div className='flex gap-2 items-center'>
<div className={cn('p-2 rounded-[3px] gap-2 w-[20px] h-[20px]', statusColor(timesheet.element[0].timesheet.status).bg)}></div>
<span className='font-bold'>{timesheet.element[0].timesheet.status === 'DENIED' ? 'REJECTED' : timesheet.element[0].timesheet.status}</span>
</div>
Expand All @@ -249,10 +249,10 @@ const MenHoursCard = ({ element, t }: MenHoursCardProps) => {
'flex flex-row-reverse justify-end items-center w-full h-[50px] rounded-sm gap-x-2 hover:no-underline px-2',
statusColor(status).text
)}>
<div className="flex items-center justify-between w-full space-x-1">
<div className="flex justify-between items-center space-x-1 w-full">
<div className="flex items-center space-x-1">
<div className={cn('p-2 rounded', statusColor(status).bg)}></div>
<div className="flex items-center gap-x-1">
<div className="flex gap-x-1 items-center">
<span className="text-base font-normal text-gray-400 uppercase">
{status === 'DENIED' ? 'REJECTED' : status}
</span>
Expand All @@ -278,7 +278,7 @@ const MenHoursCard = ({ element, t }: MenHoursCardProps) => {
borderBottomColor: statusColor(status).bg
}}
className={cn(
'flex items-center border-b border-b-gray-200 dark:border-b-gray-600 space-x-4 p-1 h-[60px]'
'flex items-center p-1 space-x-4 border-b border-b-gray-200 dark:border-b-gray-600 h-[60px]'
)} >
<div className="flex-[2]">
<TaskNameInfoDisplay
Expand All @@ -294,7 +294,7 @@ const MenHoursCard = ({ element, t }: MenHoursCardProps) => {
taskNumberClassName="text-sm"
/>
</div>
<div className="flex items-center gap-2 flex-1">
<div className="flex flex-1 gap-2 items-center">
{items.project?.imageUrl && <ProjectLogo className='w-[28px] h-[28px] drop-shadow-[25%] rounded-[8px]' imageUrl={items.project.imageUrl} />}
<span className="font-medium">{items.project?.name}</span>
</div>
Expand Down

0 comments on commit 59ab502

Please # to comment.