Skip to content
New issue

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

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

Already on GitHub? # to your account

refactor: optimize date range picker component #3592

Merged
merged 7 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
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