Skip to content

Commit c39be6e

Browse files
authored
Merge pull request #115 from jphacks/split-components
コンポーネントを分割
2 parents f8944f2 + 0de5cdc commit c39be6e

10 files changed

+997
-920
lines changed

task_yell/src/app/home/page.tsx

+49-920
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
"use client";
2+
3+
import {
4+
eachDayOfInterval,
5+
endOfMonth,
6+
endOfWeek,
7+
format,
8+
isSameDay,
9+
isSameMonth,
10+
startOfMonth,
11+
startOfWeek,
12+
} from "date-fns";
13+
import { motion } from "framer-motion";
14+
import { Event, Todo, StickyNote } from "@/components/types";
15+
import { priorityColors } from "@/components/priority-colors";
16+
17+
type Props = {
18+
todos: Todo[];
19+
events: Event[];
20+
stickyNotes: StickyNote[];
21+
currentMonth: Date;
22+
selectedDate: Date;
23+
handleDateSelect: (date: Date) => void;
24+
isDarkMode: boolean;
25+
draggedStickyNote: StickyNote | null;
26+
deleteStickyNote: (id: string) => void;
27+
setIsEventModalOpen: (isOpen: boolean) => void;
28+
}
29+
30+
function getDaysInMonth(date: Date) {
31+
const start = startOfWeek(startOfMonth(date), { weekStartsOn: 0 });
32+
const end = endOfWeek(endOfMonth(date), { weekStartsOn: 0 });
33+
return eachDayOfInterval({ start, end });
34+
}
35+
36+
function getTodoCountForDay(todos: Todo[], day: Date) {
37+
return todos.filter((todo) => isSameDay(todo.date, day)).length;
38+
}
39+
40+
function getEventCountForDay(events: Event[], day: Date) {
41+
return events.filter((event) => event.start && isSameDay(event.start, day))
42+
.length;
43+
}
44+
45+
function getTaskIndicatorStyle(isDarkMode: boolean, todoCount: number, eventCount: number) {
46+
const count = todoCount + eventCount;
47+
if (count === 0) return "";
48+
const baseColor = isDarkMode ? "bg-red-" : "bg-red-";
49+
const intensity = Math.min(count * 100, 900);
50+
const colorClass = `${baseColor}${intensity}`;
51+
return `${colorClass} ${count >= 3 ? "animate-pulse" : ""}`;
52+
}
53+
54+
export function CalendarRenderer({
55+
todos, events,
56+
currentMonth, selectedDate, handleDateSelect,
57+
isDarkMode,
58+
draggedStickyNote, deleteStickyNote,
59+
setIsEventModalOpen
60+
}: Props
61+
) {
62+
const days = getDaysInMonth(currentMonth);
63+
const weeks = Math.ceil(days.length / 7);
64+
65+
return (
66+
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-4">
67+
<div className="grid grid-cols-7 gap-1">
68+
{["日", "月", "火", "水", "木", "金", "土"].map((day) => (
69+
<div
70+
key={day}
71+
className="text-center font-semibold text-gray-600 dark:text-gray-300 p-2"
72+
>
73+
{day}
74+
</div>
75+
))}
76+
</div>
77+
{Array.from({ length: weeks }).map((_, weekIndex) => {
78+
const weekDays = days.slice(weekIndex * 7, (weekIndex + 1) * 7);
79+
const maxEventsInWeek = Math.max(
80+
...weekDays.map(
81+
(day) => getTodoCountForDay(todos, day) + getEventCountForDay(events, day),
82+
),
83+
);
84+
const weekHeight =
85+
maxEventsInWeek > 2 ? Math.min(maxEventsInWeek * 20, 100) : "auto";
86+
87+
return (
88+
<div
89+
key={weekIndex}
90+
className="grid grid-cols-7 gap-1"
91+
style={{ minHeight: "100px", height: weekHeight }}
92+
>
93+
{weekDays.map((day) => {
94+
const todoCount = getTodoCountForDay(todos, day);
95+
const eventCount = getEventCountForDay(events, day);
96+
const isSelected = isSameDay(day, selectedDate);
97+
const isCurrentMonth = isSameMonth(day, currentMonth);
98+
const dayItems = [
99+
...todos.filter((todo) => isSameDay(todo.date, day)),
100+
...events.filter(
101+
(event) => event.start && isSameDay(event.start, day),
102+
),
103+
];
104+
105+
return (
106+
<motion.div
107+
key={day.toISOString()}
108+
className={`p-1 border rounded-md cursor-pointer transition-all duration-300 overflow-hidden ${isSelected ? "border-blue-300 dark:border-blue-600" : ""} ${!isCurrentMonth
109+
? "text-gray-400 dark:text-gray-600 bg-gray-100 dark:bg-gray-700"
110+
: ""
111+
} ${getTaskIndicatorStyle(isDarkMode, todoCount, eventCount)} hover:bg-gray-100 dark:hover:bg-gray-700`}
112+
onClick={() => handleDateSelect(day)}
113+
whileHover={{ scale: 1.05 }}
114+
whileTap={{ scale: 0.95 }}
115+
onDragOver={(e) => {
116+
e.preventDefault();
117+
e.currentTarget.classList.add(
118+
"bg-blue-100",
119+
"dark:bg-blue-800",
120+
);
121+
}}
122+
onDragLeave={(e) => {
123+
e.currentTarget.classList.remove(
124+
"bg-blue-100",
125+
"dark:bg-blue-800",
126+
);
127+
}}
128+
onDrop={(e) => {
129+
e.preventDefault();
130+
e.currentTarget.classList.remove(
131+
"bg-blue-100",
132+
"dark:bg-blue-800",
133+
);
134+
if (draggedStickyNote) {
135+
handleDateSelect(day);
136+
setIsEventModalOpen(true);
137+
deleteStickyNote(draggedStickyNote.id);
138+
}
139+
}}
140+
>
141+
<div className="text-right text-sm">{format(day, "d")}</div>
142+
{(todoCount > 0 || eventCount > 0) && (
143+
<div className="mt-1 space-y-1">
144+
{dayItems.slice(0, 2).map((item, index) => (
145+
<div
146+
key={index}
147+
className={`text-xs p-1 rounded ${"text" in item ? priorityColors[item.priority] : priorityColors[item.priority]}`}
148+
>
149+
{"text" in item ? item.text : item.title}
150+
</div>
151+
))}
152+
{dayItems.length > 2 && (
153+
<div className="text-xs text-center font-bold">
154+
+{dayItems.length - 2} more
155+
</div>
156+
)}
157+
</div>
158+
)}
159+
</motion.div>
160+
);
161+
})}
162+
</div>
163+
);
164+
})}
165+
</div>
166+
);
167+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"use client";
2+
3+
import {
4+
Dialog,
5+
DialogContent,
6+
DialogHeader,
7+
DialogTitle,
8+
} from "@/components/ui/dialog";
9+
import {
10+
format,
11+
} from "date-fns";
12+
import { ja } from "date-fns/locale";
13+
import { EventCreator } from "@/components/event-creator";
14+
import { Event, StickyNote } from "@/components/types";
15+
16+
type Props = {
17+
stickyNotes: StickyNote[];
18+
setStickyNotes: (notes: StickyNote[]) => void;
19+
isEventModalOpen: boolean;
20+
setIsEventModalOpen: (isOpen: boolean) => void;
21+
selectedDate: Date;
22+
events: Event[];
23+
addEvent: (newEvent: Event, notification: { date: Date | null; type: "call" | "push" }) => void;
24+
removedStickyNote: StickyNote | null;
25+
setRemovedStickyNote: (note: StickyNote | null) => void;
26+
draggedStickyNote: StickyNote | null;
27+
};
28+
29+
export function CreateEventDialog({
30+
stickyNotes, setStickyNotes,
31+
isEventModalOpen, setIsEventModalOpen,
32+
selectedDate,
33+
events, addEvent,
34+
removedStickyNote, setRemovedStickyNote,
35+
draggedStickyNote,
36+
}: Props) {
37+
return (
38+
<Dialog open={isEventModalOpen} onOpenChange={setIsEventModalOpen}>
39+
<DialogContent className="max-w-3xl">
40+
<DialogHeader>
41+
<DialogTitle>
42+
{format(selectedDate, "yyyy年MM月dd日", { locale: ja })}の予定
43+
</DialogTitle>
44+
</DialogHeader>
45+
<EventCreator
46+
events={events}
47+
onSave={addEvent}
48+
onCancel={() => {
49+
if (removedStickyNote) {
50+
setStickyNotes([...stickyNotes, removedStickyNote]);
51+
setRemovedStickyNote(null);
52+
}
53+
setIsEventModalOpen(false);
54+
}}
55+
initialTitle={draggedStickyNote ? draggedStickyNote.title : ""}
56+
targetDate={selectedDate}
57+
/>
58+
</DialogContent>
59+
</Dialog>
60+
)
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"use client";
2+
3+
import { Button } from "@/components/ui/button";
4+
import {
5+
Dialog,
6+
DialogContent,
7+
DialogHeader,
8+
DialogTitle,
9+
} from "@/components/ui/dialog";
10+
import { Input } from "@/components/ui/input";
11+
import { StickyNote } from "./types";
12+
13+
type Props = {
14+
editingStickyNote: StickyNote | null;
15+
setEditingStickyNote: (stickyNote: StickyNote | null) => void;
16+
updateStickyNote: (stickyNote: StickyNote) => void;
17+
}
18+
19+
export function EditWantodoDialog({ editingStickyNote, setEditingStickyNote, updateStickyNote }: Props) {
20+
return (
21+
<Dialog
22+
open={!!editingStickyNote}
23+
onOpenChange={() => setEditingStickyNote(null)}
24+
>
25+
<DialogContent>
26+
<DialogHeader>
27+
<DialogTitle>wanTODOを編集</DialogTitle>
28+
</DialogHeader>
29+
{editingStickyNote && (
30+
<div className="space-y-4">
31+
<Input
32+
type="text"
33+
value={editingStickyNote.title}
34+
onChange={(e) =>
35+
setEditingStickyNote({
36+
...editingStickyNote,
37+
title: e.target.value,
38+
})
39+
}
40+
placeholder="タイトルを入力"
41+
/>
42+
<Button
43+
onClick={() =>
44+
editingStickyNote && updateStickyNote(editingStickyNote)
45+
}
46+
>
47+
更新
48+
</Button>
49+
</div>
50+
)}
51+
</DialogContent>
52+
</Dialog>
53+
)
54+
}

0 commit comments

Comments
 (0)