Skip to content

Commit 1a2067b

Browse files
authored
Merge pull request #56 from nannany/copilot/add-move-task-button
feat: タスクを今日に移動するボタンを追加
2 parents 1aa4178 + 394a18d commit 1a2067b

File tree

5 files changed

+117
-3
lines changed

5 files changed

+117
-3
lines changed

ui/src/components/task/SortableTask.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useSortable } from "@dnd-kit/sortable";
22
import { CSS } from "@dnd-kit/utilities";
33
import { Button } from "@/components/ui/button";
4-
import { GripVertical, Trash2 } from "lucide-react";
4+
import { GripVertical, Trash2, CalendarCheck } from "lucide-react";
55
import React from "react";
66
import { TaskTimerButton } from "./TaskTimerButton";
77
import { TaskTitleField } from "./TaskTitleField";
@@ -12,6 +12,7 @@ import { TaskCategoryField } from "./TaskCategoryField";
1212
import { TaskMetaInfo } from "./TaskMetaInfo";
1313
import { Task } from "@/types/task";
1414
import { useTaskContext } from "@/contexts/TaskContext";
15+
import { getTodayDateString } from "@/lib/utils";
1516

1617
interface SortableTaskProps {
1718
task: Task;
@@ -96,6 +97,15 @@ const SortableTask = ({ task }: SortableTaskProps) => {
9697
);
9798
const categoryColor = selectedCategory?.color || "#6b7280";
9899

100+
// 今日の日付を取得(JST)
101+
const todayFormatted = getTodayDateString();
102+
103+
// タスクが今日のものかどうか
104+
const isToday = task.task_date === todayFormatted;
105+
106+
// タスクが完了しているかどうか
107+
const isCompleted = !!task.end_time;
108+
99109
return (
100110
<div
101111
ref={setNodeRef}
@@ -138,6 +148,18 @@ const SortableTask = ({ task }: SortableTaskProps) => {
138148
</div>
139149

140150
<div className="flex gap-2 items-center">
151+
{/* 今日に移動ボタン:完了していない&今日以外のタスクに表示 */}
152+
{!isCompleted && !isToday && (
153+
<Button
154+
size="icon"
155+
variant="outline"
156+
onClick={() => taskActions.handleMoveToToday(task.id)}
157+
className="h-8 w-8 text-blue-500 hover:text-blue-700 hover:bg-blue-50"
158+
title="今日に移動"
159+
>
160+
<CalendarCheck className="h-4 w-4" />
161+
</Button>
162+
)}
141163
<Button
142164
size="icon"
143165
variant="outline"

ui/src/contexts/TaskContext.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export interface TaskActions {
3131
action: "start" | "stop" | "complete",
3232
) => void;
3333
handleRepeatTask: (task: Task) => void;
34+
handleMoveToToday: (taskId: string) => void;
3435
}
3536

3637
// タスクコンテキストの型定義

ui/src/hooks/useTaskActions.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from "react";
22
import { createClient } from "@/lib/supabase/client";
33
import { useToast } from "@/components/ui/use-toast";
44
import { TaskAction } from "@/types/task";
5+
import { getTodayDateString } from "@/lib/utils";
56

67
const supabase = createClient();
78

@@ -55,8 +56,38 @@ export const useTaskActions = (dispatch: React.Dispatch<TaskAction>) => {
5556
}
5657
};
5758

59+
// タスクを今日に移動
60+
const handleMoveToToday = async (taskId: string) => {
61+
const taskDate = getTodayDateString();
62+
63+
const { error } = await supabase
64+
.from("tasks")
65+
.update({ task_date: taskDate })
66+
.eq("id", taskId);
67+
68+
if (error) {
69+
toast({
70+
title: "Error",
71+
description: "Failed to move task to today",
72+
variant: "destructive",
73+
});
74+
console.error("Error moving task to today:", error);
75+
} else {
76+
// ローカル状態から削除(画面から消す)
77+
// 注:タスクは別の日付に移動しただけなので、データベースからは削除されていません。
78+
// 現在表示している日付から消すため、DELETE_TASkアクションを使用します。
79+
dispatch({ type: "DELETE_TASK", payload: taskId });
80+
81+
toast({
82+
title: "Success",
83+
description: "タスクを今日に移動しました",
84+
});
85+
}
86+
};
87+
5888
return {
5989
handleDelete,
6090
handleTaskTimer,
91+
handleMoveToToday,
6192
};
6293
};

ui/src/lib/utils.test.ts

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { describe, it, expect } from "vitest";
2-
import { parseTimeInputToISOString } from "./utils";
1+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2+
import { parseTimeInputToISOString, getTodayDateString } from "./utils";
33

44
describe("parseTimeInputToISOString", () => {
55
const baseDate = "2024-07-31"; // This will be treated as YYYY-MM-DD 00:00:00 in the local timezone by new Date()
@@ -121,3 +121,52 @@ describe("parseTimeInputToISOString", () => {
121121
);
122122
});
123123
});
124+
125+
describe("getTodayDateString", () => {
126+
beforeEach(() => {
127+
// Mock Date.prototype.toLocaleString
128+
vi.spyOn(Date.prototype, "toLocaleString").mockImplementation(
129+
// @ts-expect-error - Mocking Date.prototype.toLocaleString with simplified signature
130+
function (
131+
this: Date,
132+
locales?: string | string[],
133+
options?: Intl.DateTimeFormatOptions,
134+
) {
135+
if (locales === "ja-JP" && options?.timeZone === "Asia/Tokyo") {
136+
// Mock a specific date for testing
137+
return "2024/12/31 12:00:00";
138+
}
139+
return "";
140+
},
141+
);
142+
});
143+
144+
afterEach(() => {
145+
vi.restoreAllMocks();
146+
});
147+
148+
it("should return today's date in YYYY-MM-DD format", () => {
149+
const result = getTodayDateString();
150+
expect(result).toBe("2024-12-31");
151+
});
152+
153+
it("should pad single-digit month and day with zero", () => {
154+
// Mock a date with single-digit month and day
155+
vi.spyOn(Date.prototype, "toLocaleString").mockImplementation(
156+
// @ts-expect-error - Mocking Date.prototype.toLocaleString with simplified signature
157+
function (
158+
this: Date,
159+
locales?: string | string[],
160+
options?: Intl.DateTimeFormatOptions,
161+
) {
162+
if (locales === "ja-JP" && options?.timeZone === "Asia/Tokyo") {
163+
return "2024/1/5 12:00:00";
164+
}
165+
return "";
166+
},
167+
);
168+
169+
const result = getTodayDateString();
170+
expect(result).toBe("2024-01-05");
171+
});
172+
});

ui/src/lib/utils.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,14 @@ export const parseTimeInputToISOString = (
3838
return null;
3939
}
4040
};
41+
42+
/**
43+
* 現在の日付を YYYY-MM-DD 形式で取得(JST)
44+
*/
45+
export const getTodayDateString = (): string => {
46+
const today = new Date()
47+
.toLocaleString("ja-JP", { timeZone: "Asia/Tokyo" })
48+
.split(" ")[0];
49+
const [year, month, day] = today.split("/");
50+
return `${year}-${month.padStart(2, "0")}-${day.padStart(2, "0")}`;
51+
};

0 commit comments

Comments
 (0)