diff --git a/apps/timo-web/app/[locale]/(main)/focus/_components/FocusTaskItem.tsx b/apps/timo-web/app/[locale]/(main)/focus/_components/FocusTaskItem.tsx new file mode 100644 index 00000000..a29c3ed1 --- /dev/null +++ b/apps/timo-web/app/[locale]/(main)/focus/_components/FocusTaskItem.tsx @@ -0,0 +1,113 @@ +import { + CalendarOnIcon, + ClockOnIcon, + PlayIcon, + StopIcon, +} from "@repo/timo-design-system/icons"; +import { Checkbox, PlayButton } from "@repo/timo-design-system/ui"; +import { cn } from "@repo/timo-design-system/utils"; + +import type { FocusTaskSubtask } from "@/app/[locale]/(main)/focus/_types/task-type"; + +export interface FocusTaskItemProps { + dayNumber: string; + dayOfWeek: string; + title: string; + completed: boolean; + dateText: string; + durationText: string; + isRunning: boolean; + subtasks: FocusTaskSubtask[]; + memo?: string; + onToggleCompleted: (completed: boolean) => void; + onTogglePlay: () => void; + onToggleSubtaskCompleted: (subtaskId: number, completed: boolean) => void; +} + +export const FocusTaskItem = ({ + dayNumber, + dayOfWeek, + title, + completed, + dateText, + durationText, + isRunning, + subtasks, + memo, + onToggleCompleted, + onTogglePlay, + onToggleSubtaskCompleted, +}: FocusTaskItemProps) => { + return ( + + + {dayNumber} + {dayOfWeek} + + + + + + {title} + + + + + + + + + {dateText} + + + + + + + {durationText} + + + + + + {isRunning ? ( + + ) : ( + + )} + + + + + {subtasks.map((subtask) => ( + + + onToggleSubtaskCompleted(subtask.subtaskId, checked) + } + /> + + {subtask.content} + + + ))} + + {memo && ( + + {memo} + + )} + + + ); +}; diff --git a/apps/timo-web/app/[locale]/(main)/focus/_containers/FocusSessionContainer.tsx b/apps/timo-web/app/[locale]/(main)/focus/_containers/FocusSessionContainer.tsx new file mode 100644 index 00000000..24b12380 --- /dev/null +++ b/apps/timo-web/app/[locale]/(main)/focus/_containers/FocusSessionContainer.tsx @@ -0,0 +1,107 @@ +"use client"; + +import { TimerOnIcon } from "@repo/timo-design-system/icons"; +import { useState } from "react"; + +import type { FocusTask } from "@/app/[locale]/(main)/focus/_types/task-type"; + +import { FocusTaskItem } from "@/app/[locale]/(main)/focus/_components/FocusTaskItem"; +import { FocusHeaderContainer } from "@/app/[locale]/(main)/focus/_containers/FocusHeaderContainer"; +import { focusTaskMock } from "@/app/[locale]/(main)/focus/_mocks/task-mock"; +import { + convertDateToBadgeText, + convertDateToDayNumberText, + convertDateToDayOfWeekText, +} from "@/app/[locale]/(main)/focus/_utils/date"; +import { convertDurationToTimeText } from "@/app/[locale]/(main)/focus/_utils/duration"; +import { Timer } from "@/components/timer/Timer"; +import { TimerControls } from "@/components/timer/TimerControls"; + +export const FocusSessionContainer = () => { + const [task, setTask] = useState(focusTaskMock); + const today = new Date(); + + const handleTogglePlay = () => { + // TODO: API + setTask((prev) => ({ ...prev, isRunning: !prev.isRunning })); + }; + + const handleEnd = () => { + // TODO: API + setTask((prev) => ({ + ...prev, + isRunning: false, + completed: true, + subtasks: prev.subtasks.map((subtask) => ({ + ...subtask, + completed: true, + })), + })); + }; + + const handleAddTime = () => {}; + + const handleToggleCompleted = (completed: boolean) => { + // TODO: API + setTask((prev) => ({ + ...prev, + completed, + isRunning: completed ? false : prev.isRunning, + subtasks: prev.subtasks.map((subtask) => ({ ...subtask, completed })), + })); + }; + + const handleToggleSubtaskCompleted = ( + subtaskId: number, + completed: boolean, + ) => { + // TODO: API + setTask((prev) => ({ + ...prev, + subtasks: prev.subtasks.map((subtask) => + subtask.subtaskId === subtaskId ? { ...subtask, completed } : subtask, + ), + })); + }; + + return ( + + + + + + + + + } + time="10:30" + plannedLabel="12M" + progress={70} + size="lg" + /> + + + + + + ); +}; diff --git a/apps/timo-web/app/[locale]/(main)/focus/_mocks/task-mock.ts b/apps/timo-web/app/[locale]/(main)/focus/_mocks/task-mock.ts new file mode 100644 index 00000000..b9927a4b --- /dev/null +++ b/apps/timo-web/app/[locale]/(main)/focus/_mocks/task-mock.ts @@ -0,0 +1,15 @@ +import type { FocusTask } from "@/app/[locale]/(main)/focus/_types/task-type"; + +export const focusTaskMock: FocusTask = { + taskId: 1, + title: "앱잼 1차 과제 제출", + completed: false, + scheduledDate: new Date(2026, 6, 1), + durationSeconds: 7200, + isRunning: false, + subtasks: [ + { subtaskId: 1, content: "기획서 작성하기", completed: false }, + { subtaskId: 2, content: "와이어프레임 그리기", completed: true }, + ], + memo: "회의 전까지 초안 공유하기회의 전까지 초안 공유하기회의 전까지 초안 공유하기회의 전까지 초안 공유하기회의 전까지 초안 공유하기회의 전까지 초안 공유하기회의 전까지 초안 공유하기회의 전까지 초안 공유하기회의 전까지 초안 공유하기회의 전까지 초안 공유하기회의 전까지 초안 공유하기회의 전까지 초안 공유하기", +}; diff --git a/apps/timo-web/app/[locale]/(main)/focus/_types/task-type.ts b/apps/timo-web/app/[locale]/(main)/focus/_types/task-type.ts new file mode 100644 index 00000000..06ccb449 --- /dev/null +++ b/apps/timo-web/app/[locale]/(main)/focus/_types/task-type.ts @@ -0,0 +1,16 @@ +export interface FocusTaskSubtask { + subtaskId: number; + content: string; + completed: boolean; +} + +export interface FocusTask { + taskId: number; + title: string; + completed: boolean; + scheduledDate: Date; + durationSeconds: number; + isRunning: boolean; + subtasks: FocusTaskSubtask[]; + memo?: string; +} diff --git a/apps/timo-web/app/[locale]/(main)/focus/_utils/date.ts b/apps/timo-web/app/[locale]/(main)/focus/_utils/date.ts new file mode 100644 index 00000000..525c0776 --- /dev/null +++ b/apps/timo-web/app/[locale]/(main)/focus/_utils/date.ts @@ -0,0 +1,25 @@ +const DAY_OF_WEEK_LABELS = [ + "일요일", + "월요일", + "화요일", + "수요일", + "목요일", + "금요일", + "토요일", +]; + +export const convertDateToDayNumberText = (date: Date): string => { + return `${date.getDate()}`; +}; + +export const convertDateToDayOfWeekText = (date: Date): string => { + return DAY_OF_WEEK_LABELS[date.getDay()] ?? ""; +}; + +export const convertDateToBadgeText = (date: Date): string => { + const year = `${date.getFullYear()}`.slice(-2); + const month = `${date.getMonth() + 1}`.padStart(2, "0"); + const day = `${date.getDate()}`.padStart(2, "0"); + + return `${year}.${month}.${day}`; +}; diff --git a/apps/timo-web/app/[locale]/(main)/focus/_utils/duration.ts b/apps/timo-web/app/[locale]/(main)/focus/_utils/duration.ts new file mode 100644 index 00000000..5097d596 --- /dev/null +++ b/apps/timo-web/app/[locale]/(main)/focus/_utils/duration.ts @@ -0,0 +1,12 @@ +const SECONDS_PER_HOUR = 3600; + +const SECONDS_PER_MINUTE = 60; + +export const convertDurationToTimeText = (durationSeconds: number): string => { + const hours = Math.floor(durationSeconds / SECONDS_PER_HOUR); + const minutes = Math.floor( + (durationSeconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE, + ); + + return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}`; +}; diff --git a/apps/timo-web/app/[locale]/(main)/focus/page.tsx b/apps/timo-web/app/[locale]/(main)/focus/page.tsx index 7937631d..86c4e9b8 100644 --- a/apps/timo-web/app/[locale]/(main)/focus/page.tsx +++ b/apps/timo-web/app/[locale]/(main)/focus/page.tsx @@ -1,5 +1,5 @@ -import { FocusHeaderContainer } from "@/app/[locale]/(main)/focus/_containers/FocusHeaderContainer"; +import { FocusSessionContainer } from "@/app/[locale]/(main)/focus/_containers/FocusSessionContainer"; export default function FocusPage() { - return ; + return ; }
{dayNumber}
{dayOfWeek}
+ {title} +
+ {subtask.content} +
+ {memo} +