From 49653f719e2c7759edc9251c36b3664415ec2901 Mon Sep 17 00:00:00 2001 From: jjangminii Date: Wed, 15 Jul 2026 23:33:21 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix(web):=20=EB=B0=98=EB=B3=B5=EC=9D=BC?= =?UTF-8?q?=EC=A0=95=20=ED=88=AC=EB=91=90=EC=9D=98=20=ED=83=80=EC=9D=B4?= =?UTF-8?q?=EB=A8=B8=20=EC=B9=B4=EB=93=9C=20=EB=8F=99=EA=B8=B0=ED=99=94=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=EB=A5=BC=20=EC=88=98=EC=A0=95=ED=95=9C?= =?UTF-8?q?=EB=8B=A4=20(#236)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 타이머 시작/활성 조회 API 응답에 추가된 date 필드를 반영하도록 orval 코드를 재생성했습니다 - 활성 타이머와 투두를 매칭할 때 todoId뿐 아니라 date까지 함께 비교하도록 수정해, 반복일정 투두가 다른 날짜에서도 실행 중으로 표시되던 문제를 해결했습니다 --- .../api/generated/endpoints/timer/timer.ts | 6 ++++++ .../api/generated/endpoints/timer/timer.zod.ts | 8 ++++++++ .../generated/models/timerActiveResponse.ts | 1 + .../api/generated/models/timerStartResponse.ts | 1 + .../home/_containers/HomeTodoContainer.tsx | 4 +++- .../home/_hooks/use-home-todos-by-date.ts | 12 ++++++++++-- .../_containers/TodayTodoListContainer.tsx | 4 +++- .../today/_hooks/useTodayTodoList.ts | 18 +++++++++++++----- .../(main)/focus/_hooks/use-focus-session.ts | 5 ++++- .../detail/DetailTodoModalContainer.tsx | 8 ++++++-- apps/timo-web/schemas/timer/timer-schema.ts | 1 + 11 files changed, 56 insertions(+), 12 deletions(-) diff --git a/apps/timo-web/api/generated/endpoints/timer/timer.ts b/apps/timo-web/api/generated/endpoints/timer/timer.ts index 47dd634c..68c2c738 100644 --- a/apps/timo-web/api/generated/endpoints/timer/timer.ts +++ b/apps/timo-web/api/generated/endpoints/timer/timer.ts @@ -58,6 +58,9 @@ const withQueryKey = ( /** * 투두의 타이머를 시작합니다. * 한 번에 한 개의 타이머만 실행 가능하며, 이미 실행/일시정지 중인 타이머가 있으면 409를 반환합니다. + * + * 응답의 date는 타이머가 귀속되는 날짜(사용자 시간대 기준 시작일)입니다. + * 반복 투두는 여러 날짜에 같은 todoId로 노출되므로, 실행중 표시는 todoId가 아닌 (todoId, date) 조합으로 구분해야 합니다. * @summary 타이머 시작 */ export const startTimer = ( @@ -499,6 +502,9 @@ export const useCompleteTimer = < * 로그인한 사용자의 현재 실행 중(RUNNING/PAUSED)인 타이머를 단건 조회합니다. * 한 사용자당 시작 이후 완료/종료되지 않은 타이머는 최대 1개만 존재할 수 있습니다. * 실행 중인 타이머가 없으면 data: null을 반환합니다. + * + * 응답의 date는 타이머가 귀속되는 날짜(사용자 시간대 기준 시작일)입니다. + * 반복 투두는 여러 날짜에 같은 todoId로 노출되므로, 실행중 표시는 todoId가 아닌 (todoId, date) 조합으로 구분해야 합니다. * @summary 현재 실행 중인 타이머 조회 */ export const getActiveTimer = ( diff --git a/apps/timo-web/api/generated/endpoints/timer/timer.zod.ts b/apps/timo-web/api/generated/endpoints/timer/timer.zod.ts index ffc1e441..644d29f4 100644 --- a/apps/timo-web/api/generated/endpoints/timer/timer.zod.ts +++ b/apps/timo-web/api/generated/endpoints/timer/timer.zod.ts @@ -10,6 +10,9 @@ import * as zod from "zod"; /** * 투두의 타이머를 시작합니다. * 한 번에 한 개의 타이머만 실행 가능하며, 이미 실행/일시정지 중인 타이머가 있으면 409를 반환합니다. + * + * 응답의 date는 타이머가 귀속되는 날짜(사용자 시간대 기준 시작일)입니다. + * 반복 투두는 여러 날짜에 같은 todoId로 노출되므로, 실행중 표시는 todoId가 아닌 (todoId, date) 조합으로 구분해야 합니다. * @summary 타이머 시작 */ export const StartTimerParams = zod.object({ @@ -23,6 +26,7 @@ export const StartTimerResponse = zod.object({ .object({ timerId: zod.number(), todoId: zod.number(), + date: zod.string(), status: zod.string(), plannedSeconds: zod.number(), startedAt: zod.string(), @@ -153,6 +157,9 @@ export const CompleteTimerResponse = zod.object({ * 로그인한 사용자의 현재 실행 중(RUNNING/PAUSED)인 타이머를 단건 조회합니다. * 한 사용자당 시작 이후 완료/종료되지 않은 타이머는 최대 1개만 존재할 수 있습니다. * 실행 중인 타이머가 없으면 data: null을 반환합니다. + * + * 응답의 date는 타이머가 귀속되는 날짜(사용자 시간대 기준 시작일)입니다. + * 반복 투두는 여러 날짜에 같은 todoId로 노출되므로, 실행중 표시는 todoId가 아닌 (todoId, date) 조합으로 구분해야 합니다. * @summary 현재 실행 중인 타이머 조회 */ export const GetActiveTimerResponse = zod.object({ @@ -162,6 +169,7 @@ export const GetActiveTimerResponse = zod.object({ .object({ timerId: zod.number(), todoId: zod.number(), + date: zod.string(), todoTitle: zod.string(), iconType: zod.string().optional(), status: zod.string(), diff --git a/apps/timo-web/api/generated/models/timerActiveResponse.ts b/apps/timo-web/api/generated/models/timerActiveResponse.ts index 1002b981..b0f76183 100644 --- a/apps/timo-web/api/generated/models/timerActiveResponse.ts +++ b/apps/timo-web/api/generated/models/timerActiveResponse.ts @@ -9,6 +9,7 @@ export interface TimerActiveResponse { timerId: number; todoId: number; + date: string; todoTitle: string; iconType?: string; status: string; diff --git a/apps/timo-web/api/generated/models/timerStartResponse.ts b/apps/timo-web/api/generated/models/timerStartResponse.ts index aa2cad40..7c736fa9 100644 --- a/apps/timo-web/api/generated/models/timerStartResponse.ts +++ b/apps/timo-web/api/generated/models/timerStartResponse.ts @@ -9,6 +9,7 @@ export interface TimerStartResponse { timerId: number; todoId: number; + date: string; status: string; plannedSeconds: number; startedAt: string; diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsx index e0aae773..3d9e89b1 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsx @@ -134,7 +134,9 @@ export const HomeTodoContainer = () => { {todos.map((todo) => { const [firstSubtask] = todo.subtasks; const isActiveTodo = - activeTimer && activeTimer.todoId === todo.todoId; + activeTimer && + activeTimer.todoId === todo.todoId && + activeTimer.date === dateKey; const durationSeconds = isActiveTodo ? activeTimer.plannedSeconds + activeTimer.extendedSeconds : todo.durationSeconds; diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/use-home-todos-by-date.ts b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/use-home-todos-by-date.ts index fff1170b..8ca8719d 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/use-home-todos-by-date.ts +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/use-home-todos-by-date.ts @@ -95,7 +95,11 @@ export const useHomeTodosByDate = ( todoId: number, completed: boolean, ) => { - if (completed && activeTimer?.todoId === todoId) { + if ( + completed && + activeTimer?.todoId === todoId && + activeTimer.date === dateKey + ) { onNeedStopConfirm(dateKey, todoId); return; } @@ -155,7 +159,11 @@ export const useHomeTodosByDate = ( openTimerPanel(); } - if (activeTimer && activeTimer.todoId === todoId) { + if ( + activeTimer && + activeTimer.todoId === todoId && + activeTimer.date === dateKey + ) { changeStatus( { timerId: activeTimer.timerId, diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoListContainer.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoListContainer.tsx index 63646197..57a0180d 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoListContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoListContainer.tsx @@ -88,7 +88,9 @@ export const TodayTodoListContainer = () => {
{todos.map((todo) => { const isActiveTodo = - activeTimer && activeTimer.todoId === todo.todoId; + activeTimer && + activeTimer.todoId === todo.todoId && + activeTimer.date === todo.date; const durationSeconds = isActiveTodo ? activeTimer.plannedSeconds + activeTimer.extendedSeconds : todo.durationSeconds; diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/useTodayTodoList.ts b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/useTodayTodoList.ts index b4b49af2..f4743e0d 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/useTodayTodoList.ts +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/useTodayTodoList.ts @@ -78,14 +78,18 @@ export const useTodayTodoList = ( }; const handleToggleCompleted = (todoId: number, completed: boolean) => { - if (completed && activeTimer?.todoId === todoId) { + const dateKey = todos.find((todo) => todo.todoId === todoId)?.date; + if (!dateKey) return; + + if ( + completed && + activeTimer?.todoId === todoId && + activeTimer.date === dateKey + ) { onNeedStopConfirm(todoId); return; } - const dateKey = todos.find((todo) => todo.todoId === todoId)?.date; - if (!dateKey) return; - const previous = todos; updateTodo(todoId, (todo) => ({ ...todo, completed })); @@ -143,7 +147,11 @@ export const useTodayTodoList = ( openTimerPanel(); } - if (activeTimer && activeTimer.todoId === todoId) { + if ( + activeTimer && + activeTimer.todoId === todoId && + activeTimer.date === dateKey + ) { changeStatus( { timerId: activeTimer.timerId, diff --git a/apps/timo-web/app/[locale]/(main)/focus/_hooks/use-focus-session.ts b/apps/timo-web/app/[locale]/(main)/focus/_hooks/use-focus-session.ts index 60ccb1fe..dd1be20d 100644 --- a/apps/timo-web/app/[locale]/(main)/focus/_hooks/use-focus-session.ts +++ b/apps/timo-web/app/[locale]/(main)/focus/_hooks/use-focus-session.ts @@ -121,7 +121,10 @@ export const useFocusSession = ({ const todo = focusView.todo; const timer = - activeTimer && todo && activeTimer.todoId === todo.todoId + activeTimer && + todo && + activeTimer.todoId === todo.todoId && + activeTimer.date === focusView.date ? activeTimer : undefined; const isRunning = timer?.status === "RUNNING"; diff --git a/apps/timo-web/containers/todo-modal/detail/DetailTodoModalContainer.tsx b/apps/timo-web/containers/todo-modal/detail/DetailTodoModalContainer.tsx index 15f201e5..34ea8548 100644 --- a/apps/timo-web/containers/todo-modal/detail/DetailTodoModalContainer.tsx +++ b/apps/timo-web/containers/todo-modal/detail/DetailTodoModalContainer.tsx @@ -51,7 +51,9 @@ const DetailTodoModalQuery = ({ const { handleUpdate } = useUpdateTodoSubmit(); const { data: activeTimer } = useActiveTimer(); const todo = data?.data; - const isPlayHighlighted = !activeTimer || activeTimer.todoId === todoId; + const isPlayHighlighted = + !activeTimer || + (activeTimer.todoId === todoId && activeTimer.date === date); useEffect(() => { if (isError && error) { @@ -62,7 +64,9 @@ const DetailTodoModalQuery = ({ if (isError || !todo) return null; const timerStatus = - activeTimer?.todoId === todoId ? activeTimer.status : todo.timerStatus; + activeTimer?.todoId === todoId && activeTimer.date === date + ? activeTimer.status + : todo.timerStatus; const deleteTodo = () => { handleDelete(todoId, { diff --git a/apps/timo-web/schemas/timer/timer-schema.ts b/apps/timo-web/schemas/timer/timer-schema.ts index c28567ad..48222df8 100644 --- a/apps/timo-web/schemas/timer/timer-schema.ts +++ b/apps/timo-web/schemas/timer/timer-schema.ts @@ -4,6 +4,7 @@ import { z } from "zod"; export const activeTimerSchema = z.object({ timerId: z.number(), todoId: z.number(), + date: z.string(), iconType: z .enum(TODO_ICON_VALUES) .nullish() From 930d44b55077df8b97e13452f4be9cc9818c9bc4 Mon Sep 17 00:00:00 2001 From: Lee Hye Won Date: Thu, 16 Jul 2026 12:21:29 +0900 Subject: [PATCH 2/2] =?UTF-8?q?rename:=20=EB=AA=A8=EB=8B=AC=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EC=9D=B4=EB=A6=84=20switch=EC=97=90=EC=84=9C=20fin?= =?UTF-8?q?ish=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/timo-web/messages/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/timo-web/messages/en.json b/apps/timo-web/messages/en.json index eb0745ef..e2ca3938 100644 --- a/apps/timo-web/messages/en.json +++ b/apps/timo-web/messages/en.json @@ -249,7 +249,7 @@ "title": "Shall we stop now?", "description": "The {minutes} completed so far will be recorded in the timebox.", "continueButton": "Continue", - "switchButton": "Switch" + "switchButton": "Finish" }, "completeModal": { "title": "The task has been completed!",