Skip to content

Commit f330d38

Browse files
authored
Date Picker Integrated (#172)
1 parent 7f5188a commit f330d38

6 files changed

Lines changed: 183 additions & 35 deletions

File tree

frontend/package-lock.json

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@
276276
"queue-microtask": "^1.2.3",
277277
"react": "^18.2.0",
278278
"react-copy-to-clipboard": "^5.1.0",
279+
"react-day-picker": "^8.10.1",
279280
"react-dom": "^18.2.0",
280281
"react-icons": "^5.2.1",
281282
"react-intersection-observer": "^9.13.0",

frontend/src/components/HomeComponents/Tasks/Tasks.tsx

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ import {
6868
TasksDatabase,
6969
} from './hooks';
7070
import { debounce } from '@/components/utils/utils';
71+
import { DatePicker } from '@/components/ui/date-picker';
72+
import { format } from 'date-fns';
7173

7274
const db = new TasksDatabase();
7375
export let syncTasksWithTwAndDb: () => any;
@@ -711,26 +713,27 @@ export const Tasks = (
711713
/>
712714
</div>
713715
<div className="grid grid-cols-4 items-center gap-4">
714-
<Label
715-
htmlFor="description"
716-
className="text-right"
717-
>
716+
<Label htmlFor="due" className="text-right">
718717
Due
719718
</Label>
720-
<Input
721-
id="due"
722-
name="due"
723-
placeholder="YYYY-MM-DD"
724-
value={newTask.due}
725-
onChange={(e) =>
726-
setNewTask({
727-
...newTask,
728-
due: e.target.value,
729-
})
730-
}
731-
required
732-
className="col-span-3"
733-
/>
719+
<div className="col-span-3">
720+
<DatePicker
721+
date={
722+
newTask.due
723+
? new Date(newTask.due)
724+
: undefined
725+
}
726+
onDateChange={(date) => {
727+
setNewTask({
728+
...newTask,
729+
due: date
730+
? format(date, 'yyyy-MM-dd')
731+
: '',
732+
});
733+
}}
734+
placeholder="Select a due date"
735+
/>
736+
</div>
734737
</div>
735738
<div className="grid grid-cols-4 items-center gap-4">
736739
<Label
@@ -1480,25 +1483,27 @@ export const Tasks = (
14801483
/>
14811484
</div>
14821485
<div className="grid grid-cols-4 items-center gap-4">
1483-
<Label
1484-
htmlFor="description"
1485-
className="text-right"
1486-
>
1486+
<Label htmlFor="due" className="text-right">
14871487
Due
14881488
</Label>
1489-
<Input
1490-
id="due"
1491-
name="due"
1492-
placeholder="YYYY-MM-DD"
1493-
value={newTask.due}
1494-
onChange={(e) =>
1495-
setNewTask({
1496-
...newTask,
1497-
due: e.target.value,
1498-
})
1499-
}
1500-
className="col-span-3"
1501-
/>
1489+
<div className="col-span-3">
1490+
<DatePicker
1491+
date={
1492+
newTask.due
1493+
? new Date(newTask.due)
1494+
: undefined
1495+
}
1496+
onDateChange={(date) => {
1497+
setNewTask({
1498+
...newTask,
1499+
due: date
1500+
? format(date, 'yyyy-MM-dd')
1501+
: '',
1502+
});
1503+
}}
1504+
placeholder="Select a due date"
1505+
/>
1506+
</div>
15021507
</div>
15031508
</div>
15041509
<DialogFooter>
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import * as React from 'react';
2+
import { ChevronLeft, ChevronRight } from 'lucide-react';
3+
import { DayPicker } from 'react-day-picker';
4+
5+
import { cn } from '@/lib/utils';
6+
import { buttonVariants } from '@/components/ui/button';
7+
8+
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
9+
10+
function Calendar({
11+
className,
12+
classNames,
13+
showOutsideDays = true,
14+
...props
15+
}: CalendarProps) {
16+
return (
17+
<DayPicker
18+
showOutsideDays={showOutsideDays}
19+
className={cn('p-3', className)}
20+
classNames={{
21+
months: 'flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0',
22+
month: 'space-y-4',
23+
caption: 'flex justify-center pt-1 relative items-center',
24+
caption_label: 'text-sm font-medium',
25+
nav: 'space-x-1 flex items-center',
26+
nav_button: cn(
27+
buttonVariants({ variant: 'outline' }),
28+
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100'
29+
),
30+
nav_button_previous: 'absolute left-1',
31+
nav_button_next: 'absolute right-1',
32+
table: 'w-full border-collapse space-y-1',
33+
head_row: 'flex',
34+
head_cell:
35+
'text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]',
36+
row: 'flex w-full mt-2',
37+
cell: 'h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20',
38+
day: cn(
39+
buttonVariants({ variant: 'ghost' }),
40+
'h-9 w-9 p-0 font-normal aria-selected:opacity-100'
41+
),
42+
day_range_end: 'day-range-end',
43+
day_selected:
44+
'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground',
45+
day_today: 'bg-accent text-accent-foreground',
46+
day_outside:
47+
'day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30',
48+
day_disabled: 'text-muted-foreground opacity-50',
49+
day_range_middle:
50+
'aria-selected:bg-accent aria-selected:text-accent-foreground',
51+
day_hidden: 'invisible',
52+
...classNames,
53+
}}
54+
components={{
55+
IconLeft: () => <ChevronLeft className="h-4 w-4" />,
56+
IconRight: () => <ChevronRight className="h-4 w-4" />,
57+
}}
58+
{...props}
59+
/>
60+
);
61+
}
62+
Calendar.displayName = 'Calendar';
63+
64+
export { Calendar };
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { format } from 'date-fns';
2+
import { Calendar as CalendarIcon } from 'lucide-react';
3+
4+
import { cn } from '@/lib/utils';
5+
import { Button } from '@/components/ui/button';
6+
import { Calendar } from '@/components/ui/calendar';
7+
import {
8+
Popover,
9+
PopoverContent,
10+
PopoverTrigger,
11+
} from '@/components/ui/popover';
12+
13+
interface DatePickerProps {
14+
date: Date | undefined;
15+
onDateChange: (date: Date | undefined) => void;
16+
placeholder?: string;
17+
className?: string;
18+
}
19+
20+
export function DatePicker({
21+
date,
22+
onDateChange,
23+
placeholder = 'Pick a date',
24+
className,
25+
}: DatePickerProps) {
26+
return (
27+
<Popover modal={true}>
28+
<PopoverTrigger asChild>
29+
<Button
30+
variant={'outline'}
31+
type="button"
32+
className={cn(
33+
'w-full justify-start text-left font-normal',
34+
!date && 'text-muted-foreground',
35+
className
36+
)}
37+
>
38+
<CalendarIcon className="mr-2 h-4 w-4" />
39+
{date ? format(date, 'PPP') : <span>{placeholder}</span>}
40+
</Button>
41+
</PopoverTrigger>
42+
<PopoverContent
43+
className="w-auto p-0"
44+
align="start"
45+
onOpenAutoFocus={(e) => e.preventDefault()}
46+
>
47+
<Calendar
48+
mode="single"
49+
selected={date}
50+
onSelect={onDateChange}
51+
initialFocus
52+
/>
53+
</PopoverContent>
54+
</Popover>
55+
);
56+
}

frontend/src/lib/utils.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
import { type ClassValue, clsx } from 'clsx';
2+
import { twMerge } from 'tailwind-merge';
3+
4+
export function cn(...inputs: ClassValue[]) {
5+
return twMerge(clsx(inputs));
6+
}
7+
18
export const BlueHeading = ({
29
prefix,
310
suffix,

0 commit comments

Comments
 (0)