Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

コンポーネントを分割 #115

Merged
merged 8 commits into from
Nov 23, 2024
969 changes: 49 additions & 920 deletions task_yell/src/app/home/page.tsx

Large diffs are not rendered by default.

167 changes: 167 additions & 0 deletions task_yell/src/components/calendar-renderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
"use client";

import {
eachDayOfInterval,
endOfMonth,
endOfWeek,
format,
isSameDay,
isSameMonth,
startOfMonth,
startOfWeek,
} from "date-fns";
import { motion } from "framer-motion";
import { Event, Todo, StickyNote } from "@/components/types";
import { priorityColors } from "@/components/priority-colors";

type Props = {
todos: Todo[];
events: Event[];
stickyNotes: StickyNote[];
currentMonth: Date;
selectedDate: Date;
handleDateSelect: (date: Date) => void;
isDarkMode: boolean;
draggedStickyNote: StickyNote | null;
deleteStickyNote: (id: string) => void;
setIsEventModalOpen: (isOpen: boolean) => void;
}

function getDaysInMonth(date: Date) {
const start = startOfWeek(startOfMonth(date), { weekStartsOn: 0 });
const end = endOfWeek(endOfMonth(date), { weekStartsOn: 0 });
return eachDayOfInterval({ start, end });
}

function getTodoCountForDay(todos: Todo[], day: Date) {
return todos.filter((todo) => isSameDay(todo.date, day)).length;
}

function getEventCountForDay(events: Event[], day: Date) {
return events.filter((event) => event.start && isSameDay(event.start, day))
.length;
}

function getTaskIndicatorStyle(isDarkMode: boolean, todoCount: number, eventCount: number) {
const count = todoCount + eventCount;
if (count === 0) return "";
const baseColor = isDarkMode ? "bg-red-" : "bg-red-";
const intensity = Math.min(count * 100, 900);
const colorClass = `${baseColor}${intensity}`;
return `${colorClass} ${count >= 3 ? "animate-pulse" : ""}`;
}

export function CalendarRenderer({
todos, events,
currentMonth, selectedDate, handleDateSelect,
isDarkMode,
draggedStickyNote, deleteStickyNote,
setIsEventModalOpen
}: Props
) {
const days = getDaysInMonth(currentMonth);
const weeks = Math.ceil(days.length / 7);

return (
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-4">
<div className="grid grid-cols-7 gap-1">
{["日", "月", "火", "水", "木", "金", "土"].map((day) => (
<div
key={day}
className="text-center font-semibold text-gray-600 dark:text-gray-300 p-2"
>
{day}
</div>
))}
</div>
{Array.from({ length: weeks }).map((_, weekIndex) => {
const weekDays = days.slice(weekIndex * 7, (weekIndex + 1) * 7);
const maxEventsInWeek = Math.max(
...weekDays.map(
(day) => getTodoCountForDay(todos, day) + getEventCountForDay(events, day),
),
);
const weekHeight =
maxEventsInWeek > 2 ? Math.min(maxEventsInWeek * 20, 100) : "auto";

return (
<div
key={weekIndex}
className="grid grid-cols-7 gap-1"
style={{ minHeight: "100px", height: weekHeight }}
>
{weekDays.map((day) => {
const todoCount = getTodoCountForDay(todos, day);
const eventCount = getEventCountForDay(events, day);
const isSelected = isSameDay(day, selectedDate);
const isCurrentMonth = isSameMonth(day, currentMonth);
const dayItems = [
...todos.filter((todo) => isSameDay(todo.date, day)),
...events.filter(
(event) => event.start && isSameDay(event.start, day),
),
];

return (
<motion.div
key={day.toISOString()}
className={`p-1 border rounded-md cursor-pointer transition-all duration-300 overflow-hidden ${isSelected ? "border-blue-300 dark:border-blue-600" : ""} ${!isCurrentMonth
? "text-gray-400 dark:text-gray-600 bg-gray-100 dark:bg-gray-700"
: ""
} ${getTaskIndicatorStyle(isDarkMode, todoCount, eventCount)} hover:bg-gray-100 dark:hover:bg-gray-700`}
onClick={() => handleDateSelect(day)}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onDragOver={(e) => {
e.preventDefault();
e.currentTarget.classList.add(
"bg-blue-100",
"dark:bg-blue-800",
);
}}
onDragLeave={(e) => {
e.currentTarget.classList.remove(
"bg-blue-100",
"dark:bg-blue-800",
);
}}
onDrop={(e) => {
e.preventDefault();
e.currentTarget.classList.remove(
"bg-blue-100",
"dark:bg-blue-800",
);
if (draggedStickyNote) {
handleDateSelect(day);
setIsEventModalOpen(true);
deleteStickyNote(draggedStickyNote.id);
}
}}
>
<div className="text-right text-sm">{format(day, "d")}</div>
{(todoCount > 0 || eventCount > 0) && (
<div className="mt-1 space-y-1">
{dayItems.slice(0, 2).map((item, index) => (
<div
key={index}
className={`text-xs p-1 rounded ${"text" in item ? priorityColors[item.priority] : priorityColors[item.priority]}`}
>
{"text" in item ? item.text : item.title}
</div>
))}
{dayItems.length > 2 && (
<div className="text-xs text-center font-bold">
+{dayItems.length - 2} more
</div>
)}
</div>
)}
</motion.div>
);
})}
</div>
);
})}
</div>
);
};
61 changes: 61 additions & 0 deletions task_yell/src/components/create-event-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"use client";

import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
format,
} from "date-fns";
import { ja } from "date-fns/locale";
import { EventCreator } from "@/components/event-creator";
import { Event, StickyNote } from "@/components/types";

type Props = {
stickyNotes: StickyNote[];
setStickyNotes: (notes: StickyNote[]) => void;
isEventModalOpen: boolean;
setIsEventModalOpen: (isOpen: boolean) => void;
selectedDate: Date;
events: Event[];
addEvent: (newEvent: Event, notification: { date: Date | null; type: "call" | "push" }) => void;
removedStickyNote: StickyNote | null;
setRemovedStickyNote: (note: StickyNote | null) => void;
draggedStickyNote: StickyNote | null;
};

export function CreateEventDialog({
stickyNotes, setStickyNotes,
isEventModalOpen, setIsEventModalOpen,
selectedDate,
events, addEvent,
removedStickyNote, setRemovedStickyNote,
draggedStickyNote,
}: Props) {
return (
<Dialog open={isEventModalOpen} onOpenChange={setIsEventModalOpen}>
<DialogContent className="max-w-3xl">
<DialogHeader>
<DialogTitle>
{format(selectedDate, "yyyy年MM月dd日", { locale: ja })}の予定
</DialogTitle>
</DialogHeader>
<EventCreator
events={events}
onSave={addEvent}
onCancel={() => {
if (removedStickyNote) {
setStickyNotes([...stickyNotes, removedStickyNote]);
setRemovedStickyNote(null);
}
setIsEventModalOpen(false);
}}
initialTitle={draggedStickyNote ? draggedStickyNote.title : ""}
targetDate={selectedDate}
/>
</DialogContent>
</Dialog>
)
}
54 changes: 54 additions & 0 deletions task_yell/src/components/edit-wantodo-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"use client";

import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { StickyNote } from "./types";

type Props = {
editingStickyNote: StickyNote | null;
setEditingStickyNote: (stickyNote: StickyNote | null) => void;
updateStickyNote: (stickyNote: StickyNote) => void;
}

export function EditWantodoDialog({ editingStickyNote, setEditingStickyNote, updateStickyNote }: Props) {
return (
<Dialog
open={!!editingStickyNote}
onOpenChange={() => setEditingStickyNote(null)}
>
<DialogContent>
<DialogHeader>
<DialogTitle>wanTODOを編集</DialogTitle>
</DialogHeader>
{editingStickyNote && (
<div className="space-y-4">
<Input
type="text"
value={editingStickyNote.title}
onChange={(e) =>
setEditingStickyNote({
...editingStickyNote,
title: e.target.value,
})
}
placeholder="タイトルを入力"
/>
<Button
onClick={() =>
editingStickyNote && updateStickyNote(editingStickyNote)
}
>
更新
</Button>
</div>
)}
</DialogContent>
</Dialog>
)
}
Loading
Loading