Skip to content

Commit 5e1753c

Browse files
committed
wip: dashboard date picker
1 parent 52686f9 commit 5e1753c

File tree

7 files changed

+177
-21
lines changed

7 files changed

+177
-21
lines changed

app/(protected)/dashboard/page.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import { getAnalysis } from '@/lib/api/analysis'
1515
import { SITE_NAME } from '@/lib/consts'
1616
import type { Metadata } from 'next'
1717
import Link from 'next/link'
18+
import { LibrarySelector } from '@/components/dashboard/LibrarySelector'
19+
import { DateRangeSelector } from '@/components/dashboard/DateRangeSelector'
1820

1921
export const metadata: Metadata = {
2022
title: `Dashboard · ${SITE_NAME}`,
@@ -71,10 +73,12 @@ export default async function Dashboard({
7173
</Breadcrumb>
7274

7375
<div className="grid my-4 grid-cols-1 gap-4 md:grid-cols-2">
74-
<MontlyBorrowChart data={res.data.borrowing} />
75-
<MonthlyRevenueChart data={res.data.revenue} />
76+
<LibrarySelector />
77+
<DateRangeSelector />
7678
<MostBorrowedBookChart data={res.data.book} />
7779
<TopMembershipChart data={res.data.membership} />
80+
<MontlyBorrowChart data={res.data.borrowing} />
81+
<MonthlyRevenueChart data={res.data.revenue} />
7882
</div>
7983
</div>
8084
)
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
'use client'
2+
3+
import * as React from 'react'
4+
import { addDays, format } from 'date-fns'
5+
import { Calendar as CalendarIcon } from 'lucide-react'
6+
import { DateRange } from 'react-day-picker'
7+
8+
import { cn } from '@/lib/utils'
9+
import { Button } from '@/components/ui/button'
10+
import { Calendar } from '@/components/ui/calendar'
11+
import {
12+
Popover,
13+
PopoverContent,
14+
PopoverTrigger,
15+
} from '@/components/ui/popover'
16+
17+
export function DateRangeSelector({
18+
className,
19+
}: React.HTMLAttributes<HTMLDivElement>) {
20+
const [date, setDate] = React.useState<DateRange | undefined>({
21+
from: new Date(2022, 0, 20),
22+
to: addDays(new Date(2022, 0, 20), 20),
23+
})
24+
25+
return (
26+
<div className={cn('grid gap-2', className)}>
27+
<Popover>
28+
<PopoverTrigger asChild>
29+
<Button
30+
id="date"
31+
variant={'outline'}
32+
className={cn(
33+
'w-[300px] justify-start text-left font-normal',
34+
!date && 'text-muted-foreground'
35+
)}
36+
>
37+
<CalendarIcon />
38+
{date?.from ? (
39+
date.to ? (
40+
<>
41+
{format(date.from, 'LLL dd, y')} -{' '}
42+
{format(date.to, 'LLL dd, y')}
43+
</>
44+
) : (
45+
format(date.from, 'LLL dd, y')
46+
)
47+
) : (
48+
<span>Pick a date</span>
49+
)}
50+
</Button>
51+
</PopoverTrigger>
52+
<PopoverContent className="w-auto p-0" align="start">
53+
<Calendar
54+
initialFocus
55+
mode="range"
56+
defaultMonth={date?.from}
57+
selected={date}
58+
onSelect={setDate}
59+
numberOfMonths={2}
60+
/>
61+
</PopoverContent>
62+
</Popover>
63+
</div>
64+
)
65+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
'use client'
2+
3+
export function LibrarySelector() {
4+
return <div>Select library</div>
5+
}

components/ui/button.tsx

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,34 @@
1-
import * as React from 'react'
2-
import { Slot } from '@radix-ui/react-slot'
3-
import { cva, type VariantProps } from 'class-variance-authority'
1+
import * as React from "react"
2+
import { Slot } from "@radix-ui/react-slot"
3+
import { cva, type VariantProps } from "class-variance-authority"
44

5-
import { cn } from '@/lib/utils'
5+
import { cn } from "@/lib/utils"
66

77
const buttonVariants = cva(
8-
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
8+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
99
{
1010
variants: {
1111
variant: {
12-
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
12+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
1313
destructive:
14-
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
14+
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
1515
outline:
16-
'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
16+
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
1717
secondary:
18-
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
19-
ghost: 'hover:bg-accent hover:text-accent-foreground',
20-
link: 'text-primary underline-offset-4 hover:underline',
18+
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
19+
ghost: "hover:bg-accent hover:text-accent-foreground",
20+
link: "text-primary underline-offset-4 hover:underline",
2121
},
2222
size: {
23-
default: 'h-10 px-4 py-2',
24-
sm: 'h-9 rounded-md px-3',
25-
lg: 'h-11 rounded-md px-8',
26-
icon: 'h-10 w-10',
23+
default: "h-10 px-4 py-2",
24+
sm: "h-9 rounded-md px-3",
25+
lg: "h-11 rounded-md px-8",
26+
icon: "h-10 w-10",
2727
},
2828
},
2929
defaultVariants: {
30-
variant: 'default',
31-
size: 'default',
30+
variant: "default",
31+
size: "default",
3232
},
3333
}
3434
)
@@ -41,7 +41,7 @@ export interface ButtonProps
4141

4242
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
4343
({ className, variant, size, asChild = false, ...props }, ref) => {
44-
const Comp = asChild ? Slot : 'button'
44+
const Comp = asChild ? Slot : "button"
4545
return (
4646
<Comp
4747
className={cn(buttonVariants({ variant, size, className }))}
@@ -51,6 +51,6 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
5151
)
5252
}
5353
)
54-
Button.displayName = 'Button'
54+
Button.displayName = "Button"
5555

5656
export { Button, buttonVariants }

components/ui/calendar.tsx

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"use client"
2+
3+
import * as React from "react"
4+
import { ChevronLeft, ChevronRight } from "lucide-react"
5+
import { DayPicker } from "react-day-picker"
6+
7+
import { cn } from "@/lib/utils"
8+
import { buttonVariants } from "@/components/ui/button"
9+
10+
export type CalendarProps = React.ComponentProps<typeof DayPicker>
11+
12+
function Calendar({
13+
className,
14+
classNames,
15+
showOutsideDays = true,
16+
...props
17+
}: CalendarProps) {
18+
return (
19+
<DayPicker
20+
showOutsideDays={showOutsideDays}
21+
className={cn("p-3", className)}
22+
classNames={{
23+
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
24+
month: "space-y-4",
25+
caption: "flex justify-center pt-1 relative items-center",
26+
caption_label: "text-sm font-medium",
27+
nav: "space-x-1 flex items-center",
28+
nav_button: cn(
29+
buttonVariants({ variant: "outline" }),
30+
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
31+
),
32+
nav_button_previous: "absolute left-1",
33+
nav_button_next: "absolute right-1",
34+
table: "w-full border-collapse space-y-1",
35+
head_row: "flex",
36+
head_cell:
37+
"text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
38+
row: "flex w-full mt-2",
39+
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",
40+
day: cn(
41+
buttonVariants({ variant: "ghost" }),
42+
"h-9 w-9 p-0 font-normal aria-selected:opacity-100"
43+
),
44+
day_range_end: "day-range-end",
45+
day_selected:
46+
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
47+
day_today: "bg-accent text-accent-foreground",
48+
day_outside:
49+
"day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
50+
day_disabled: "text-muted-foreground opacity-50",
51+
day_range_middle:
52+
"aria-selected:bg-accent aria-selected:text-accent-foreground",
53+
day_hidden: "invisible",
54+
...classNames,
55+
}}
56+
components={{
57+
IconLeft: ({ className, ...props }) => (
58+
<ChevronLeft className={cn("h-4 w-4", className)} {...props} />
59+
),
60+
IconRight: ({ className, ...props }) => (
61+
<ChevronRight className={cn("h-4 w-4", className)} {...props} />
62+
),
63+
}}
64+
{...props}
65+
/>
66+
)
67+
}
68+
Calendar.displayName = "Calendar"
69+
70+
export { Calendar }

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@
2424
"class-variance-authority": "^0.7.1",
2525
"clsx": "^2.1.1",
2626
"cmdk": "1.0.0",
27+
"date-fns": "^4.1.0",
2728
"firebase": "^11.1.0",
2829
"firebase-admin": "^13.0.2",
2930
"lucide-react": "^0.469.0",
3031
"next": "15.1.0",
3132
"react": "^19.0.0",
33+
"react-day-picker": "8.10.1",
3234
"react-dom": "^19.0.0",
3335
"react-hook-form": "^7.54.1",
3436
"recharts": "^2.15.0",

yarn.lock

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2236,6 +2236,11 @@ data-view-byte-offset@^1.0.0:
22362236
es-errors "^1.3.0"
22372237
is-data-view "^1.0.1"
22382238

2239+
date-fns@^4.1.0:
2240+
version "4.1.0"
2241+
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-4.1.0.tgz#64b3d83fff5aa80438f5b1a633c2e83b8a1c2d14"
2242+
integrity sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==
2243+
22392244
debug@4, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.7:
22402245
version "4.4.0"
22412246
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a"
@@ -4114,6 +4119,11 @@ queue-microtask@^1.2.2:
41144119
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
41154120
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
41164121

4122+
react-day-picker@8.10.1:
4123+
version "8.10.1"
4124+
resolved "https://registry.yarnpkg.com/react-day-picker/-/react-day-picker-8.10.1.tgz#4762ec298865919b93ec09ba69621580835b8e80"
4125+
integrity sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==
4126+
41174127
react-dom@^19.0.0:
41184128
version "19.0.0"
41194129
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.0.0.tgz#43446f1f01c65a4cd7f7588083e686a6726cfb57"

0 commit comments

Comments
 (0)