Skip to content

Commit 085cd6a

Browse files
authored
feat: create DashboardCalendar component
2 parents 26b22cb + 24dc2a1 commit 085cd6a

File tree

2 files changed

+124
-0
lines changed

2 files changed

+124
-0
lines changed

src/app/page.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import FilterMenu from "@/components/FilterMenu";
22
import Navbar from "@/components/Navbar";
33
import RecipeSummary from "@/components/RecipeSummary";
4+
import DashboardCalendar from "@/components/DashboardCalendar";
45
import WeeklyMenu from "@/components/WeeklyMenu";
56
import ComboCard from "@/components/ComboCard";
67
import CalendarRecipeItem from "@/components/CalendarRecipeItem";
@@ -21,6 +22,11 @@ export default function Home() {
2122
<h1 className="text-3xl font-bold">Dashboard</h1>
2223
<p className="mt-2 text-lg text-gray-600">{formattedDate}</p>
2324
</div>
25+
<div className="px-6">
26+
<div className="max-w-lg">
27+
<DashboardCalendar />
28+
</div>
29+
</div>
2430
<h1>Dashboard</h1>
2531
<h1 className="text-3xl font-bold underline">Hello world!</h1>
2632
<WeeklyMenu dateToday={today} />
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import { ChevronLeft, ChevronRight } from "lucide-react";
5+
6+
const DAY_HEADERS = ["SUN", "MON", "TUE", "WED", "THUR", "FRI", "SAT"];
7+
8+
function getDaysInMonth(year: number, month: number): number {
9+
return new Date(year, month + 1, 0).getDate();
10+
}
11+
12+
function getFirstDayOfMonth(year: number, month: number): number {
13+
return new Date(year, month, 1).getDay();
14+
}
15+
16+
function getWeekRange(date: Date): { start: Date; end: Date } {
17+
const day = date.getDay();
18+
const start = new Date(date);
19+
start.setDate(date.getDate() - day);
20+
const end = new Date(start);
21+
end.setDate(start.getDate() + 6);
22+
return { start, end };
23+
}
24+
25+
function isSameDay(a: Date, b: Date): boolean {
26+
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
27+
}
28+
29+
function isInSameWeekWeekday(selected: Date, date: Date): boolean {
30+
const { start, end } = getWeekRange(selected);
31+
const dayOfWeek = date.getDay();
32+
return date >= start && date <= end && dayOfWeek >= 1 && dayOfWeek <= 5;
33+
}
34+
35+
export default function DashboardCalendar() {
36+
const [currentMonth, setCurrentMonth] = useState(new Date());
37+
const [selectedDate, setSelectedDate] = useState<Date | null>(null);
38+
const year = currentMonth.getFullYear();
39+
const month = currentMonth.getMonth();
40+
const daysInMonth = getDaysInMonth(year, month);
41+
const firstDay = getFirstDayOfMonth(year, month);
42+
const monthName = currentMonth.toLocaleDateString("en-US", {
43+
month: "long",
44+
year: "numeric",
45+
});
46+
47+
function goToPrevMonth() {
48+
setCurrentMonth(new Date(year, month - 1, 1));
49+
}
50+
51+
function goToNextMonth() {
52+
setCurrentMonth(new Date(year, month + 1, 1));
53+
}
54+
55+
function handleDateClick(day: number) {
56+
setSelectedDate(new Date(year, month, day));
57+
}
58+
59+
return (
60+
<div className="w-full font-montserrat">
61+
{/* Header */}
62+
<div className="mb-6 flex items-center justify-between">
63+
<h2 className="text-3xl font-bold">{monthName}</h2>
64+
<div className="flex gap-2">
65+
<button
66+
onClick={goToPrevMonth}
67+
className="rounded-full p-1 transition-colors hover:bg-gray-100"
68+
aria-label="Previous month"
69+
>
70+
<ChevronLeft className="h-6 w-6 text-pepper" />
71+
</button>
72+
<button
73+
onClick={goToNextMonth}
74+
className="rounded-full p-1 transition-colors hover:bg-gray-100"
75+
aria-label="Next month"
76+
>
77+
<ChevronRight className="h-6 w-6 text-pepper" />
78+
</button>
79+
</div>
80+
</div>
81+
{/* Day headers */}
82+
<div className="mb-4 grid grid-cols-7">
83+
{DAY_HEADERS.map((day) => (
84+
<div key={day} className="text-sm font-bold">
85+
{day}
86+
</div>
87+
))}
88+
</div>
89+
{/* Calendar grid */}
90+
<div className="grid grid-cols-7 gap-y-6">
91+
{Array.from({ length: firstDay }).map((_, i) => (
92+
<div key={`empty-${i}`} />
93+
))}
94+
{Array.from({ length: daysInMonth }).map((_, i) => {
95+
const day = i + 1;
96+
const date = new Date(year, month, day);
97+
const isSelected = selectedDate !== null && isSameDay(date, selectedDate);
98+
const isSameWeek = selectedDate !== null && !isSelected && isInSameWeekWeekday(selectedDate, date);
99+
return (
100+
<button
101+
key={day}
102+
onClick={() => handleDateClick(day)}
103+
className={`flex h-10 w-10 items-center justify-center rounded-full text-base transition-colors ${
104+
isSelected
105+
? "bg-radish-900 text-white"
106+
: isSameWeek
107+
? "bg-radish-100 text-radish-900"
108+
: "text-pepper hover:bg-gray-100"
109+
}`}
110+
>
111+
{day}
112+
</button>
113+
);
114+
})}
115+
</div>
116+
</div>
117+
);
118+
}

0 commit comments

Comments
 (0)