Skip to content

Commit d959d12

Browse files
committed
feat: Add task repeat functionality with hover button for task timer
1 parent ab9c35c commit d959d12

File tree

3 files changed

+105
-14
lines changed

3 files changed

+105
-14
lines changed

ui/src/components/task/SortableTask.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ interface TaskActions {
3939
taskId: string,
4040
action: "start" | "stop" | "complete",
4141
) => void;
42+
handleRepeatTask: (task: Task) => void;
4243
}
4344

4445
interface SortableTaskProps {
@@ -164,6 +165,7 @@ const SortableTask = ({
164165
<TaskTimerButton
165166
task={task}
166167
onTaskTimer={taskActions.handleTaskTimer}
168+
onRepeatTask={taskActions.handleRepeatTask}
167169
categoryColor={categoryColor}
168170
/>
169171

ui/src/components/task/TaskTimerButton.tsx

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
import { Button } from "@/components/ui/button";
2-
import { Play, Square, CheckCircle2 } from "lucide-react";
2+
import { Play, Square, CheckCircle2, RotateCcw } from "lucide-react";
33
import { Task } from "./types";
4+
import { useState } from "react";
45

56
interface TaskTimerButtonProps {
67
task: Task;
78
onTaskTimer: (taskId: string, action: "start" | "stop" | "complete") => void;
9+
onRepeatTask?: (task: Task) => void;
810
categoryColor?: string;
911
}
1012

1113
export const TaskTimerButton = ({
1214
task,
1315
onTaskTimer,
16+
onRepeatTask,
1417
categoryColor = "#6b7280",
1518
}: TaskTimerButtonProps) => {
19+
const [isHovered, setIsHovered] = useState(false);
1620
if (!task.start_time) {
1721
return (
1822
<Button
@@ -50,18 +54,42 @@ export const TaskTimerButton = ({
5054
}
5155

5256
return (
53-
<Button
54-
size="icon"
55-
variant="outline"
56-
className="h-8 w-8"
57-
style={{
58-
color: categoryColor,
59-
borderColor: categoryColor,
60-
backgroundColor: `${categoryColor}20`,
61-
}}
62-
disabled
57+
<div
58+
className="relative"
59+
onMouseEnter={() => setIsHovered(true)}
60+
onMouseLeave={() => setIsHovered(false)}
6361
>
64-
<CheckCircle2 className="h-4 w-4" />
65-
</Button>
62+
<Button
63+
size="icon"
64+
variant="outline"
65+
className="h-8 w-8"
66+
style={{
67+
color: categoryColor,
68+
borderColor: categoryColor,
69+
backgroundColor: `${categoryColor}20`,
70+
}}
71+
disabled
72+
>
73+
<CheckCircle2 className="h-4 w-4" />
74+
</Button>
75+
76+
{isHovered && onRepeatTask && (
77+
<Button
78+
size="icon"
79+
variant="outline"
80+
className="absolute -top-2 -right-2 h-6 w-6 bg-white shadow-md hover:bg-gray-50"
81+
onClick={(e) => {
82+
e.stopPropagation();
83+
onRepeatTask(task);
84+
}}
85+
style={{
86+
color: categoryColor,
87+
borderColor: categoryColor,
88+
}}
89+
>
90+
<RotateCcw className="h-3 w-3" />
91+
</Button>
92+
)}
93+
</div>
6694
);
6795
};

ui/src/pages/TaskList/TaskList.tsx

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,64 @@ const TaskList = () => {
208208
return `${year}-${month.padStart(2, "0")}-${day.padStart(2, "0")}`;
209209
};
210210

211+
// タスク繰り返し機能
212+
const handleRepeatTask = async (originalTask: Task) => {
213+
if (!selectedDate || !user?.id) {
214+
toast({
215+
title: "Error",
216+
description: "User not authenticated or date not selected",
217+
variant: "destructive",
218+
});
219+
return;
220+
}
221+
222+
startTransition(async () => {
223+
// 同じ名前、同じカテゴリーで新しいタスクを作成
224+
const newTask = {
225+
title: originalTask.title,
226+
description: originalTask.description,
227+
user_id: user.id,
228+
estimated_minute: originalTask.estimated_minute,
229+
category_id: originalTask.category_id,
230+
task_date: convertDateStringToDate(
231+
selectedDate
232+
.toLocaleString("ja-JP", { timeZone: "Asia/Tokyo" })
233+
.split(" ")[0],
234+
),
235+
};
236+
237+
const { data, error } = await supabase
238+
.from("tasks")
239+
.insert(newTask)
240+
.select();
241+
242+
if (error) {
243+
toast({
244+
title: "Error",
245+
description: "Failed to repeat task",
246+
variant: "destructive",
247+
});
248+
console.error("Error repeating task:", error);
249+
} else {
250+
const createdTask = (data?.[0] as unknown as Task) || ({} as Task);
251+
252+
// 新しく作成したタスクをリストに追加
253+
dispatch({
254+
type: "ADD_TASK",
255+
payload: createdTask,
256+
});
257+
258+
// 新しいタスクを自動的に開始
259+
taskActions.handleTaskTimer(createdTask.id, "start");
260+
261+
toast({
262+
title: "Success",
263+
description: `タスク "${originalTask.title}" を繰り返し作成し、開始しました`,
264+
});
265+
}
266+
});
267+
};
268+
211269
// クイックタスク追加
212270
const handleAddTask = async () => {
213271
if (!newTaskTitle.trim()) {
@@ -512,7 +570,10 @@ const TaskList = () => {
512570
key={task.id}
513571
task={task}
514572
taskEdit={taskEdit}
515-
taskActions={taskActions}
573+
taskActions={{
574+
...taskActions,
575+
handleRepeatTask,
576+
}}
516577
lastTaskEndTime={lastTaskEndTime}
517578
categories={categories}
518579
/>

0 commit comments

Comments
 (0)