[FIX] 상세 TODO 조회/수정/삭제 API 연결 개선#210
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
Next review available in: 44 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (12)
Walkthrough상세 투두 모달의 업데이트 요청을 텍스트·서브태스크 중심의 디바운스 방식으로 분리하고, 필드별 패치 핸들러와 오버레이 기반 모달 수명 주기를 적용했습니다. 홈·오늘 카드 및 모달의 플레이 버튼은 클릭과 포인터 이벤트 전파를 차단합니다. Changes투두 상세 모달 및 카드 상호작용
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant DetailTodoModalContent
participant UpdateHandlers
participant UpdateRequestBuilder
participant UpdateAPI
User->>DetailTodoModalContent: 필드 변경
DetailTodoModalContent->>UpdateHandlers: 변경 이벤트 전달
UpdateHandlers->>UpdateRequestBuilder: 변경 데이터 정규화
UpdateHandlers->>UpdateAPI: 디바운스 또는 즉시 PATCH 요청
UpdateAPI-->>DetailTodoModalContent: 업데이트 결과
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Timo Performance ReportBundle Size — timo-web
Lighthouse — timo-web
Image Optimization — timo-web
측정 커밋: |
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@apps/timo-web/app/`[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeTodoCard.tsx:
- Around line 130-135: Remove the redundant onPointerDownCapture handler from
the interactive wrapper using stopInteractiveEvent, keeping the onPointerDown
handler and existing onClick behavior unchanged.
In `@apps/timo-web/app/`[locale]/(main)/today/_components/TodayTodoCard.tsx:
- Around line 66-71: Remove the duplicate stopPlayEvent handler and reuse the
existing stopPropagation utility defined immediately above it for the relevant
event handlers in TodayTodoCard.
- Line 16: Remove the unused MouseEvent and PointerEvent type imports from
TodayTodoCard.tsx after removing stopPlayEvent, while retaining KeyboardEvent
and ReactNode if they are still referenced.
- Around line 132-136: Replace the stopPlayEvent usage in the onClick and
onPointerDown handlers of TodayTodoCard with the existing stopPropagation
function, preserving the current event-stopping behavior while keeping onPlay
execution unchanged.
In `@apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx`:
- Around line 189-209: handleDateChange와 handleTimeChange에서 로컬 폼 상태를 변경한 뒤
updateTodo가 실패하면 이전 날짜·시간 값으로 롤백하거나 재조회하도록 useUpdateTodoSubmit의 onError를 연결하세요.
실패 시 사용자에게 토스트를 표시하고, 성공 시에는 기존 업데이트 동작을 유지하세요.
In `@apps/timo-web/containers/todo-modal/detail/DetailTodoModalContainer.tsx`:
- Line 49: Update the error path in DetailTodoModalContainer so a failed or
missing todo does not return null before OverlayModal cleanup runs. Render the
error modal or explicitly invoke the existing close and unmount handling,
ensuring overlay.open is cleared and the user can close or retry.
- Around line 54-69: The todo update submission currently does not propagate
mutation results to the content component, so failed updates are treated as
completed. In DetailTodoModalContainer.tsx, update updateTodo to pass onSuccess
and onError callbacks through useUpdateTodoSubmit: update
lastSubmittedTextUpdateSignatureRef only on success and show the existing error
toast on failure so retry remains possible. Apply the corresponding callback
handling in DetailTodoModalContent.tsx at the affected submission site; the
container and content must preserve success-only submission confirmation and
failure retry behavior.
In `@apps/timo-web/utils/todo/detail-todo-update-request.ts`:
- Around line 23-32: Update buildDetailTodoSubtasksUpdateRequest to apply
truncateToWeightedLength to each trimmed subtask value before filtering and
constructing the PATCH payload, using the same weighted-length limit and
behavior already applied to todo titles; ensure over-limit subtasks are
truncated rather than sent unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 00bb9372-1b37-494b-9388-d5c3bd52a74c
📒 Files selected for processing (7)
apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeTodoCard.tsxapps/timo-web/app/[locale]/(main)/today/_components/TodayTodoCard.tsxapps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsxapps/timo-web/components/todo-modal/detail/DetailTodoTaskFields.tsxapps/timo-web/containers/todo-modal/detail/DetailTodoModalContainer.tsxapps/timo-web/hooks/todo-modal/detail/use-detail-todo-form.tsapps/timo-web/utils/todo/detail-todo-update-request.ts
| const closeDetailTodoModal = () => { | ||
| setIsOpen(false); | ||
| }; | ||
| if (isError || !todo) return null; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
조회 실패 시 overlay를 반드시 종료하세요.
Line 49에서 바로 null을 반환하면 OverlayModal이 렌더링되지 않아 onExited도 호출되지 않습니다. 따라서 overlay.open 항목이 보이지 않는 상태로 남고 사용자는 닫거나 재시도할 수 없습니다. 오류 모달을 렌더링하거나 오류 시 close와 unmount를 명시적으로 처리해 주세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/timo-web/containers/todo-modal/detail/DetailTodoModalContainer.tsx` at
line 49, Update the error path in DetailTodoModalContainer so a failed or
missing todo does not return null before OverlayModal cleanup runs. Render the
error modal or explicitly invoke the existing close and unmount handling,
ensuring overlay.open is cleared and the user can close or retry.
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (2)
apps/timo-web/containers/todo-modal/detail/DetailTodoModalContainer.tsx (2)
76-87: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
updateTodo에onSuccess가 없어서 실패한 수정도 "성공"처럼 처리됩니다.
handleUpdate에onError만 넘기고onSuccess는 넘기지 않아서, 하위 훅들(use-detail-todo-patch-handlers.ts의 날짜/시간 롤백,use-detail-todo-text-auto-save.ts의lastSubmittedTextUpdateSignatureRef)이 성공 여부를 알 방법이 없습니다. 바로 위deleteTodo(66-74행)는onSuccess/onError를 둘 다 활용하는 대조적인 패턴이라,updateTodo도 같은 방식으로 맞춰주면 좋겠습니다. 자세한 내용은 아래 통합 코멘트에 정리했어요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/timo-web/containers/todo-modal/detail/DetailTodoModalContainer.tsx` around lines 76 - 87, Update updateTodo to pass an onSuccess callback to handleUpdate, matching the existing deleteTodo pattern and preserving the onError callback. Ensure the success callback is propagated so the detail todo patch handlers and text auto-save logic can distinguish successful updates from failures.
55-61: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win조회 실패 시 여전히 overlay가 정리되지 않습니다.
새
onActionError훅으로 에러 토스트는 뜨지만, 61행에서 바로null을 반환하는 구조는 그대로라OverlayModal이 한 번도 렌더링되지 않고onExited(=unmount)도 호출되지 않습니다. 토스트로 실패는 알 수 있어도, overlay 스택에는 사용자가 닫을 수 없는 "빈 항목"이 남게 돼요. 이전 리뷰에서 지적된 사항이 이번 변경에도 그대로 남아있습니다.에러 상태를 위한 최소 UI를 렌더링하거나,
close/unmount를 명시적으로 호출해 정리해 주세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/timo-web/containers/todo-modal/detail/DetailTodoModalContainer.tsx` around lines 55 - 61, Update DetailTodoModalContainer so the isError or missing-todo path does not return null while the overlay remains mounted. Render the minimum error-state UI needed for OverlayModal to invoke its onExited/unmount cleanup, or explicitly call the existing close/unmount action before returning; preserve the current onActionError toast behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/timo-web/hooks/todo-modal/detail/use-detail-todo-icon-submit.ts`:
- Around line 6-51: Connect handleRemoveIcon to the persistence flow instead of
only clearing pendingIcon: extend TodoUpdateRequest.icon with the PATCH deletion
representation and add the corresponding removeIcon submission callback or
payload handling. Update handleSubmitIcon and handleToggleIconPanel so a pending
null submits removal, while unchanged selections still close without updating
and non-null selections retain the existing selectIcon/onUpdate behavior.
In `@apps/timo-web/hooks/todo-modal/detail/use-detail-todo-patch-handlers.ts`:
- Around line 46-57: Update handleDateChange and handleTimeChange to preserve
the previous form values and roll them back when updateTodo fails. Extend the
surrounding updateTodo/onUpdate flow to expose mutation failure through the
existing success/error callback mechanism, then restore the prior date or time
only on failure while retaining the optimistic update on success.
In `@apps/timo-web/hooks/todo-modal/detail/use-detail-todo-text-auto-save.ts`:
- Around line 53-61: Update submitTextUpdate and its surrounding update flow so
lastSubmittedTextUpdateSignatureRef is recorded only after the PATCH succeeds,
rather than immediately after the void onUpdate call. Propagate or reuse the
container’s updateTodo success signal through the callback contract, while
preserving the existing duplicate-signature guard and retrying failed updates
when the text remains unchanged.
---
Duplicate comments:
In `@apps/timo-web/containers/todo-modal/detail/DetailTodoModalContainer.tsx`:
- Around line 76-87: Update updateTodo to pass an onSuccess callback to
handleUpdate, matching the existing deleteTodo pattern and preserving the
onError callback. Ensure the success callback is propagated so the detail todo
patch handlers and text auto-save logic can distinguish successful updates from
failures.
- Around line 55-61: Update DetailTodoModalContainer so the isError or
missing-todo path does not return null while the overlay remains mounted. Render
the minimum error-state UI needed for OverlayModal to invoke its
onExited/unmount cleanup, or explicitly call the existing close/unmount action
before returning; preserve the current onActionError toast behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: eb9a4df8-325b-457a-89b3-637a1a20e3d4
📒 Files selected for processing (8)
apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsxapps/timo-web/containers/todo-modal/detail/DetailTodoModalContainer.tsxapps/timo-web/hooks/todo-modal/detail/use-delete-todo-submit.tsapps/timo-web/hooks/todo-modal/detail/use-detail-todo-form.tsapps/timo-web/hooks/todo-modal/detail/use-detail-todo-icon-submit.tsapps/timo-web/hooks/todo-modal/detail/use-detail-todo-patch-handlers.tsapps/timo-web/hooks/todo-modal/detail/use-detail-todo-text-auto-save.tsapps/timo-web/utils/todo/todo-time.ts
| export interface UseDetailTodoIconSubmitParams { | ||
| icon: TodoIconValue | null; | ||
| selectIcon: (icon: TodoIconValue) => void; | ||
| onUpdate: (data: TodoUpdateRequest) => void; | ||
| } | ||
|
|
||
| export const useDetailTodoIconSubmit = ({ | ||
| icon, | ||
| selectIcon, | ||
| onUpdate, | ||
| }: UseDetailTodoIconSubmitParams) => { | ||
| const [isIconPanelOpen, setIsIconPanelOpen] = useState(false); | ||
| const [pendingIcon, setPendingIcon] = useState<TodoIconValue | null>(icon); | ||
|
|
||
| const handleSelectIcon = (nextIcon: TodoIconValue) => { | ||
| setPendingIcon(nextIcon); | ||
| }; | ||
|
|
||
| const handleOpenIconPanel = () => { | ||
| setPendingIcon(icon); | ||
| setIsIconPanelOpen(true); | ||
| }; | ||
|
|
||
| const handleSubmitIcon = () => { | ||
| if (!pendingIcon || pendingIcon === icon) { | ||
| setIsIconPanelOpen(false); | ||
| return; | ||
| } | ||
|
|
||
| selectIcon(pendingIcon); | ||
| onUpdate({ icon: pendingIcon }); | ||
| setIsIconPanelOpen(false); | ||
| }; | ||
|
|
||
| const handleToggleIconPanel = () => { | ||
| if (isIconPanelOpen) { | ||
| handleSubmitIcon(); | ||
| return; | ||
| } | ||
|
|
||
| handleOpenIconPanel(); | ||
| }; | ||
|
|
||
| const handleRemoveIcon = () => { | ||
| setPendingIcon(null); | ||
| }; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
fd 'todoUpdateRequestIcon' apps/timo-web/api/generated -a
rg -n "TodoUpdateRequestIcon" apps/timo-web/api/generated -g '*.ts' -A5Repository: Team-Timo/Timo-client
Length of output: 2281
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "## skill"
cat -n .agents/skills/quality/timo-review/SKILL.md
echo "## hook file"
cat -n apps/timo-web/hooks/todo-modal/detail/use-detail-todo-icon-submit.ts
echo "## form file"
cat -n apps/timo-web/hooks/todo-modal/detail/use-detail-todo-form.ts | sed -n '1,220p'
echo "## generated update request"
cat -n apps/timo-web/api/generated/models/todoUpdateRequest.ts
echo "## icon submit usages"
rg -n "useDetailTodoIconSubmit|handleRemoveIcon|removeIcon|onRemoveIcon|selectIcon" apps/timo-web -g '*.ts' -g '*.tsx' -A3 -B3Repository: Team-Timo/Timo-client
Length of output: 25410
🏁 Script executed:
#!/bin/bash
set -euo pipefail
cat -n .agents/skills/quality/timo-review/SKILL.md
printf '\n---\n'
cat -n apps/timo-web/hooks/todo-modal/detail/use-detail-todo-icon-submit.ts
printf '\n---\n'
cat -n apps/timo-web/hooks/todo-modal/detail/use-detail-todo-form.ts | sed -n '1,220p'
printf '\n---\n'
cat -n apps/timo-web/api/generated/models/todoUpdateRequest.ts
printf '\n---\n'
rg -n "useDetailTodoIconSubmit|handleRemoveIcon|removeIcon|onRemoveIcon|selectIcon" apps/timo-web -g '*.ts' -g '*.tsx' -A3 -B3Repository: Team-Timo/Timo-client
Length of output: 25345
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "## patch handlers"
cat -n apps/timo-web/hooks/todo-modal/detail/use-detail-todo-patch-handlers.ts
echo "## detail modal"
cat -n apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx | sed -n '1,220p'
echo "## icon selector/remove flow"
cat -n apps/timo-web/components/todo-modal/common/TodoIconField.tsx | sed -n '1,220p'
echo "## update todo usages"
rg -n "updateTodo\(|patchHandlers\.updateTodo|onUpdate\(" apps/timo-web -g '*.ts' -g '*.tsx' -A3 -B3Repository: Team-Timo/Timo-client
Length of output: 30776
아이콘 제거를 PATCH 경로에 연결해 주세요
handleRemoveIcon이 로컬 pendingIcon만 null로 바꾸고 끝나서, 제거가 저장되지 않습니다. TodoUpdateRequest.icon도 enum만 허용해 삭제 표현이 없으니, 현재 구조에선 패널을 닫을 때 원래 아이콘으로 되돌아갑니다. 제거를 지원하려면 removeIcon까지 제출 흐름에 연결하고, PATCH 스펙에 삭제 표현을 추가해야 합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/timo-web/hooks/todo-modal/detail/use-detail-todo-icon-submit.ts` around
lines 6 - 51, Connect handleRemoveIcon to the persistence flow instead of only
clearing pendingIcon: extend TodoUpdateRequest.icon with the PATCH deletion
representation and add the corresponding removeIcon submission callback or
payload handling. Update handleSubmitIcon and handleToggleIconPanel so a pending
null submits removal, while unchanged selections still close without updating
and non-null selections retain the existing selectIcon/onUpdate behavior.
ISSUE 🔗
close #202
What is this PR? 🔍
상세 TODO 모달의 조회/수정/삭제 API 연결 구조를 정리하고, generated API 훅을 기준으로 요청이 나가도록 수정했습니다.
기존에는 상세 모달 내부에서 API 요청 흐름과 UI 상태 관리가 섞여 있어 어떤 동작에서 어떤 요청이 나가는지 추적하기 어려웠습니다. 이번 작업에서는 상세 모달을 overlay 기반 컨테이너 구조로 분리하고, 상세 조회/수정/삭제 요청을 각각 담당 훅을 통해 처리하도록 정리했습니다.
해결한 문제
useGetTodoDetail,useUpdateTodo,useDeleteTodo흐름을 사용하도록 정리했습니다.왜 이 방법을 사용했는지
상세 TODO 모달은 조회, 수정, 삭제가 모두 연결되는 화면이라 UI 컴포넌트 안에 API 로직이 직접 섞이면 변경 추적이 어려워집니다.
그래서 다음 기준으로 구조를 정리했습니다.
DetailTodoModalContainer에서 처리useGetTodoDetail사용useUpdateTodo를 감싼useUpdateTodoSubmit에서 처리useDeleteTodo를 감싼useDeleteTodoSubmit에서 처리DetailTodoModalContent에서 렌더링과 입력 상태 관리에 집중이렇게 나누면 어떤 UI 동작이 어떤 PATCH body를 만드는지 확인하기 쉬워지고, 이후 에러 처리나 토스트 처리를 추가할 때도 한 곳에서 관리하기 쉬워집니다.
어떻게 해결했는지
상세 조회
DetailTodoModalContainer에서todoId와date를 기준으로 상세 TODO를 조회합니다.조회된 데이터는 DetailTodoModalContent로 내려서 UI에 표시합니다.
상세 수정
수정은 generated useUpdateTodo를 사용하는 useUpdateTodoSubmit을 통해 처리합니다.
각 입력 변경 시 전체 TODO 데이터를 보내지 않고 변경된 필드만 PATCH body로 전달합니다.
예시:
수정 성공 후에는 홈 목록과 상세 조회 쿼리를 invalidate하여 최신 데이터를 다시 가져오도록 했습니다.
상세 삭제
삭제는 generated useDeleteTodo를 사용하는 useDeleteTodoSubmit을 통해 처리합니다.
삭제 성공 시 홈 목록 쿼리를 invalidate하고, 모달을 닫도록 처리했습니다.
아이콘 수정
기존에는 아이콘을 선택하는 순간 PATCH 요청이 나갔습니다.
이번 수정에서는 pendingIcon 상태를 따로 두어 아이콘 선택과 저장을 분리했습니다.
이를 통해 사용자가 아이콘을 둘러보는 과정에서 불필요한 API 요청이 나가지 않도록 했습니다.
플레이 버튼 이벤트
홈 카드와 상세 모달 내부 플레이 버튼에서 stopPropagation을 처리했습니다.
카드 자체의 클릭/드래그 이벤트와 플레이 버튼 클릭 이벤트가 섞이지 않도록 분리하기 위해서입니다.
To Reviewers
현재 해결하지 못한 부분
날짜, AI 추천 시간, 드롭다운 수정 시 409 응답
현재 프론트에서는 generated API 기준으로 PATCH 요청을 정상적으로 보내고 있습니다.
예시:
하지만 일부 필드 수정 시 서버에서 409 응답이 반환됩니다.
아이콘 변경은 동일한 PATCH API에서 정상 처리되는 것으로 보아, 프론트에서 요청 자체가 누락되는 문제라기보다는 서버의 비즈니스 검증에서 거절되는 케이스로 보입니다.
특히 날짜 변경의 경우 상세 조회는 todoId + date 기준으로 이루어지지만, 수정 API는 PATCH /api/v1/todos/{todoId} 형태라 서버가 “어느 날짜 인스턴스를 수정하는지” 판단하기 어려울 수 있습니다.
Screenshot 📷
Test Checklist ✔