Skip to content

Commit 5c868a4

Browse files
committed
feat: Add CurrentTaskFooter component to display current task details and elapsed time
1 parent 450b338 commit 5c868a4

File tree

2 files changed

+128
-1
lines changed

2 files changed

+128
-1
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { useEffect, useState } from "react";
2+
import { Task, Category } from "@/types/task";
3+
import { Square } from "lucide-react";
4+
import { Button } from "@/components/ui/button";
5+
6+
interface CurrentTaskFooterProps {
7+
currentTask: Task | null;
8+
categories: Category[];
9+
onTaskTimer: (taskId: string, action: "start" | "stop" | "complete") => void;
10+
}
11+
12+
export const CurrentTaskFooter = ({
13+
currentTask,
14+
categories,
15+
onTaskTimer,
16+
}: CurrentTaskFooterProps) => {
17+
const [currentTime, setCurrentTime] = useState(new Date());
18+
const [elapsedTime, setElapsedTime] = useState(0);
19+
20+
// 1秒ごとに現在時刻を更新
21+
useEffect(() => {
22+
const timer = setInterval(() => {
23+
setCurrentTime(new Date());
24+
}, 1000);
25+
26+
return () => clearInterval(timer);
27+
}, []);
28+
29+
// 経過時間を計算
30+
useEffect(() => {
31+
if (currentTask?.start_time) {
32+
const startTime = new Date(currentTask.start_time);
33+
const elapsed = Math.floor((currentTime.getTime() - startTime.getTime()) / 1000);
34+
setElapsedTime(elapsed);
35+
} else {
36+
setElapsedTime(0);
37+
}
38+
}, [currentTask?.start_time, currentTime]);
39+
40+
// 経過時間をフォーマット
41+
const formatElapsedTime = (seconds: number) => {
42+
const hours = Math.floor(seconds / 3600);
43+
const minutes = Math.floor((seconds % 3600) / 60);
44+
const secs = seconds % 60;
45+
46+
if (hours > 0) {
47+
return `${hours}:${minutes.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
48+
}
49+
return `${minutes}:${secs.toString().padStart(2, "0")}`;
50+
};
51+
52+
if (!currentTask) {
53+
return null;
54+
}
55+
56+
const category = categories.find((cat) => cat.id === currentTask.category_id);
57+
const categoryColor = category?.color || "#6b7280";
58+
59+
return (
60+
<div
61+
className="fixed bottom-0 left-0 right-0 bg-white border-t shadow-lg p-4 z-50"
62+
style={{ borderTopColor: categoryColor }}
63+
>
64+
<div className="max-w-4xl mx-auto flex items-center justify-between">
65+
<div className="flex items-center gap-4">
66+
<div
67+
className="w-3 h-3 rounded-full animate-pulse"
68+
style={{ backgroundColor: categoryColor }}
69+
/>
70+
<div>
71+
<p className="font-medium text-gray-900">{currentTask.title}</p>
72+
<div className="flex items-center gap-2 text-sm text-gray-600">
73+
{category && (
74+
<span style={{ color: categoryColor }}>
75+
{category.name}
76+
</span>
77+
)}
78+
<span></span>
79+
<span>開始: {new Date(currentTask.start_time!).toLocaleTimeString("ja-JP", {
80+
hour: "2-digit",
81+
minute: "2-digit"
82+
})}</span>
83+
</div>
84+
</div>
85+
</div>
86+
87+
<div className="flex items-center gap-4">
88+
<div className="text-right">
89+
<p className="text-2xl font-mono font-bold" style={{ color: categoryColor }}>
90+
{formatElapsedTime(elapsedTime)}
91+
</p>
92+
<p className="text-xs text-gray-500">経過時間</p>
93+
</div>
94+
95+
<Button
96+
size="sm"
97+
variant="outline"
98+
onClick={() => onTaskTimer(currentTask.id, "stop")}
99+
className="hover:bg-red-50"
100+
style={{
101+
color: "#dc2626",
102+
borderColor: "#dc2626",
103+
}}
104+
>
105+
<Square className="h-4 w-4 mr-1" />
106+
停止
107+
</Button>
108+
</div>
109+
</div>
110+
</div>
111+
);
112+
};

ui/src/pages/TaskList/TaskList.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { Task, Category } from "@/types/task";
4141
import { taskReducer } from "@/reducers/taskReducer";
4242
import { useTaskEdit } from "@/hooks/useTaskEdit";
4343
import { useTaskActions } from "@/hooks/useTaskActions";
44+
import { CurrentTaskFooter } from "@/components/CurrentTaskFooter";
4445

4546
const supabase = createClient();
4647

@@ -121,6 +122,11 @@ const TaskList = () => {
121122
return latest;
122123
}, null);
123124

125+
// 現在実行中のタスクを取得(start_timeがあってend_timeがないタスク)
126+
const currentRunningTask = tasks.find(
127+
(task) => task.start_time && !task.end_time
128+
);
129+
124130
// ドラッグ&ドロップのセンサーを設定
125131
const sensors = useSensors(
126132
useSensor(PointerSensor, {
@@ -387,7 +393,8 @@ const TaskList = () => {
387393
};
388394

389395
return (
390-
<div className="space-y-6">
396+
<>
397+
<div className="space-y-6 pb-20">
391398
<div className="flex items-center justify-between">
392399
<div>
393400
{totalEstimatedMinutes > 0 && (
@@ -522,6 +529,14 @@ const TaskList = () => {
522529
</CardContent>
523530
</Card>
524531
</div>
532+
533+
{/* 現在実行中のタスクフッター */}
534+
<CurrentTaskFooter
535+
currentTask={currentRunningTask || null}
536+
categories={categories}
537+
onTaskTimer={taskActions.handleTaskTimer}
538+
/>
539+
</>
525540
);
526541
};
527542

0 commit comments

Comments
 (0)