Skip to content

[FEAT] 홈 화면 7일 뷰 퍼블리싱 및 투두 드래그 정렬/타이머 연동 구현#124

Merged
kimminna merged 11 commits into
developfrom
feat/web/121-home-view-publishing
Jul 10, 2026
Merged

[FEAT] 홈 화면 7일 뷰 퍼블리싱 및 투두 드래그 정렬/타이머 연동 구현#124
kimminna merged 11 commits into
developfrom
feat/web/121-home-view-publishing

Conversation

@kimminna

@kimminna kimminna commented Jul 8, 2026

Copy link
Copy Markdown
Member

ISSUE 🔗

close #121



What is this PR? 🔍

홈 화면을 기본 뷰(가로 스크롤)와 7일 뷰로 퍼블리싱하고, 투두 카드 드래그 정렬과 재생 버튼 클릭 시 타이머 사이드바 자동 오픈을 구현했습니다. 진행 중 발견한 세로 스크롤 레이아웃 붕괴 버그를 함께 수정했습니다.

배경

  • 기존 구조: 홈 화면은 목데이터 하나만 렌더링하는 자리표시자 수준이었고, 투두 카드는 순서 변경이나 타이머 사이드바와의 연동이 없었습니다.
  • 발생 문제: 7일 뷰로 확장하는 과정에서 투두 카드가 세로 스크롤 리스트 안에서 부모 높이를 강제로 채우려 해 카드가 여러 개일 때 서로 겹치는 레이아웃 붕괴가 발생했습니다.
  • 해결 방향: 날짜 기준(DEFAULT/WEEK) mock 데이터 레이어를 분리하고, 카드 높이를 콘텐츠 기준으로 되돌린 뒤 dnd-kit·zustand를 도입해 정렬과 사이드바 상태를 연결했습니다.

홈 7일 뷰 & 데이터 레이어

  • 변경 요약: 필터(DEFAULT/WEEK)와 기준일 기반 home-view mock 데이터 레이어를 구성하고, 헤더의 뷰 전환을 실제 데이터 조회 및 "오늘"로 스크롤 이동과 연결했습니다.
  • 이유: 기존에는 뷰 전환 UI만 있고 실제로 날짜 범위를 바꿔 데이터를 다시 그리는 로직이 없었습니다.
  • 구현 방식: useHomeViewMode가 뷰 모드와 기준일을 관리하고, getHomeViewMock({ filter, baseDate })가 이를 받아 날짜별 Todo[]를 생성합니다. 기본 뷰는 reorderDaysTodayFirst로 오늘 날짜를 맨 앞으로 회전시키고, 7일 뷰는 날짜 컬럼과 카드가 남은 공간을 유동적으로 나눠 가지되 최소 150px 폭을 보장하며 부족하면 가로 스크롤되도록 했습니다. priority/요일 i18n 메시지 키를 API enum 값(대문자)과 통일해 중복 라벨 매핑 유틸을 제거했습니다.

투두 드래그 앤 드롭 정렬

  • 변경 요약: 투두 카드를 드래그로 순서 변경할 수 있는 기능을 추가했습니다.
  • 이유: 같은 날짜 안에서 투두 우선순위를 사용자가 직접 조정할 방법이 없었습니다.
  • 구현 방식: @dnd-kit/core, @dnd-kit/sortable를 도입해 DndSortableListProvider(providers/dnd/)가 DndContext + SortableContext를 구성하고, 각 HomeTodoCarduseSortable로 드래그 핸들을 갖습니다. 드롭 시 useHomeTodosByDate.handleReorderTodo가 로컬 상태를 낙관적으로 재정렬한 뒤 patchTodoOrderMock을 호출하고, 실패하면 이전 상태로 롤백합니다. 헤더/투두 상태 로직은 각각 HomeDayHeaderContainer, useHomeTodosByDate로 분리해 HomeTodoContainer의 책임을 줄였습니다.
  • 경계 · 제약: 실제 정렬 API는 아직 없어 patchTodoOrderMock으로 대체했습니다 (// TODO: API).

재생 버튼 → 타이머 사이드바 연동

  • 변경 요약: 투두 카드의 재생 버튼을 눌러 타이머가 시작되면 오른쪽 타임 사이드바가 열리고 Timer 탭으로 전환되도록 연동했습니다.
  • 이유: 사이드바 열림/탭 상태가 WithTimeSidebarContainer·TimeSidebar 각각의 로컬 useState였기 때문에, 트리 깊숙한 곳(투두 카드)에서 상태를 바꿀 방법이 없었습니다.
  • 구현 방식: docs/architecture/state.md의 "클라이언트 전역 상태 → Zustand" 컨벤션에 따라 zustand를 새로 추가하고 stores/time-sidebar/useTimeSidebarStore.tsisOpen/activeTab/openTimerPanel 등을 정의했습니다. WithTimeSidebarContainerTimeSidebar는 각자의 로컬 상태를 이 스토어로 교체했고, useHomeTodosByDate.handleTogglePlay는 타이머가 STOPPED→RUNNING으로 전환될 때만 openTimerPanel()을 호출합니다(정지 시에는 호출하지 않음).
  • 경계 · 제약: React Context로 구현하는 대안도 검토했으나, 프로젝트 컨벤션과 향후 다른 전역 상태 재사용을 고려해 Zustand를 선택했습니다.

파일 구조 정리

  • 변경 요약: QueryProviderproviders/query/로, 토스트 컨테이너 2종을 home/_containers/toast/로 이동했습니다(내용 변경 없는 순수 이동).
  • 이유: docs/architecture/web/coding-style.md의 "파일 타입이 아닌 기능 단위 폴더" 컨벤션에 맞추기 위함입니다.
  • 경계 · 제약: 이동에 따라 깨진 import 경로(layout.tsxQueryProvider, HomeTodoContainer.tsxDndSortableListProvider)를 함께 수정했습니다.

AddTaskButton 유동 너비 (@repo/timo-design-system)

  • 변경 요약: 고정 min-width를 제거해 부모 폭에 맞춰 유동적으로 늘어나거나 줄어들도록 하고, default와 폭 차이만 있던 weekly variant를 제거했습니다.
  • 이유: 7일 뷰에서 날짜 컬럼 폭이 150px~가변으로 좁아질 수 있는데 고정 폭 버튼이 컬럼을 밀어내는 문제가 있었습니다.

유틸 함수 문서화

  • 변경 요약: date, day-of-week, home-view, todo-time 유틸 함수에 동작을 설명하는 JSDoc을 추가했습니다.
  • 이유: 날짜 회전·범위 계산 로직이 비자명해 함수 시그니처만으로는 의도가 드러나지 않았습니다.



To Reviewers

  • 정렬(handleReorderTodo)과 재생(handleTogglePlay) 모두 실제 API 대신 mock 함수를 호출합니다. 실제 API 연동은 후속 작업입니다.
  • 사이드바 상태를 Zustand 전역 스토어로 옮긴 구조가 적절한지 한 번 봐주세요



Screenshot 📷

Animation



Test Checklist ✔

  • pnpm check-types 통과
  • pnpm lint 통과
  • pnpm build — 미실행: 로컬에서 실행하지 않음
  • 브라우저에서 7일 뷰/드래그 정렬/타이머 사이드바 연동 동작 확인 — 미실행: 이번 세션에는 브라우저 확인 도구가 연결되지 않음

Summary by CodeRabbit

  • New Features
    • 홈 화면에 주간/기본 보기 전환과 날짜 이동 기능이 추가되었습니다.
    • 할 일 목록이 날짜별로 나뉘어 표시되며, 드래그로 순서를 바꿀 수 있습니다.
    • 완료 항목은 드래그가 제한되고, 오늘로 빠르게 이동할 수 있습니다.
  • Bug Fixes
    • 할 일 카드의 우선순위, 요일 표기가 새 번역 체계에 맞게 정리되었습니다.
    • 사이드바와 홈 화면 스크롤 동작이 더 자연스럽게 개선되었습니다.

kimminna added 11 commits July 8, 2026 02:19
useSearchParams 등 클라이언트 훅 사용 시 필요한 Suspense 경계와, 향후 비동기 데이터 페칭 시 필요한 에러 처리를 공통 AsyncBoundary 컴포넌트로 추가했습니다.
- 고정 min-width를 제거하고 부모 폭에 맞춰 유동적으로 늘어나거나 줄어들도록 했습니다
- default와 폭 차이가 유일한 구분점이던 weekly variant를 제거했습니다
- 텍스트 span을 항상 truncate 가능하도록 통일했습니다
- 필터(DEFAULT/WEEK)·기준일 기반 home-view mock 데이터 레이어를 구성했습니다
- 헤더의 뷰 전환(기본/7일)을 실제 데이터 조회 및 오늘로 스크롤 이동과 연결했습니다
- 7일 뷰에서 날짜 컬럼과 투두 카드가 남은 공간을 유동적으로 나눠 갖도록 하고, 최소 150px 폭을 보장하며 부족하면 가로 스크롤되도록 했습니다
- priority/요일 i18n 메시지 키를 API enum 값(대문자)과 통일해 중복된 라벨 매핑 유틸을 제거했습니다
…to feat/web/121-home-view-publishing

# Conflicts:
#	apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsx
- 투두 순서 변경을 위해 dnd-kit 패키지를 추가했습니다
- 타이머 사이드바 전역 상태 관리를 위해 zustand 패키지를 추가했습니다
- 투두 카드를 드래그로 순서 변경할 수 있는 기능을 추가했습니다
- 헤더/투두 상태 로직을 HomeDayHeaderContainer, useHomeTodosByDate로 분리했습니다
- 태그 이름을 i18n 키 기반으로 매핑하도록 변경했습니다
- 토스트 컨테이너를 home/_containers/toast/ 하위로 이동했습니다
- QueryProvider를 providers/query/ 하위로 이동했습니다
- date, day-of-week, home-view, todo-time 유틸 함수에 동작 설명을 추가했습니다
- 카드가 부모 컨테이너의 높이를 강제로 채우면서 여러 개일 때 서로 겹치던 문제를 수정했습니다
- 카드 높이를 콘텐츠 크기에 맞게 자연스럽게 계산되도록 변경했습니다
- 타이머 사이드바 열림/탭 상태를 zustand 전역 스토어로 옮겨 깊은 트리와 공유했습니다
- 투두 카드의 재생 버튼을 눌러 타이머가 시작되면 사이드바가 열리고 Timer 탭으로 전환되도록 연동했습니다
…view-publishing

# Conflicts:
#	apps/timo-web/components/boundary/AsyncBoundary.tsx
#	apps/timo-web/components/boundary/ErrorBoundary.tsx
@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
timo Ready Ready Preview, Comment Jul 8, 2026 2:12pm

@github-actions github-actions Bot added ⏰ Timo-web Timo 웹 서비스 ⌚ Timo-Design-system Timo 디자인 시스템 labels Jul 8, 2026
@github-actions github-actions Bot added ✨ Feature 새로운 기능(기능성) 구현 ♦️ 민아 민아상 labels Jul 8, 2026
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

Storybook Preview

항목 링크
Storybook 열기
Chromatic 빌드 확인

마지막 업데이트: 2026-07-08 14:13 UTC

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

Timo Performance Report

Bundle Size — timo-web
라우트 크기 First Load JS
/[locale] 0 B 🟡 205.79 kB
/[locale]/focus 0 B 🟡 205.79 kB
/[locale]/home 0 B 🟡 205.79 kB
/[locale]/onboarding 0 B 🟡 205.79 kB
/[locale]/settings 0 B 🟡 205.79 kB
/[locale]/settings/account 0 B 🟡 205.79 kB
/[locale]/settings/policy 0 B 🟡 205.79 kB
/[locale]/statistics 0 B 🟡 205.79 kB
/[locale]/today 0 B 🟡 205.79 kB

공유 번들: 205.79 kB
🟢 < 200kB  |  🟡 < 350kB  |  🔴 ≥ 350kB (First Load JS · gzip)

Lighthouse — timo-web

⚠️ Lighthouse 결과를 가져오지 못했습니다.

Image Optimization — timo-web

public/ 디렉토리에 이미지가 없습니다.

측정 커밋: eda4d6e

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Caution

Review failed

An error occurred during the review process. Please try again later.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

홈 화면에 일간(15일 스크롤)/주간(7일) 뷰와 드래그 앤 드롭 투두 재정렬 기능이 추가되었다. 관련하여 홈 뷰/Todo 타입이 Zod 스키마 기반으로 전환되고, mock 데이터/날짜 유틸/신규 훅이 추가되었다. 별도로 타임 사이드바 상태가 Zustand 스토어로 전환되고, 디자인 시스템 AddTaskButtonweekly variant가 제거되었으며, 요일/우선순위 i18n 키가 API enum 값에 맞춰 갱신되었다.

Changes

홈 화면 일간/주간 뷰 및 정렬 기능

Layer / File(s) Summary
홈 뷰/Todo 스키마
home/_types/home-view-type.ts, home/_types/todo-type.ts
Zod 기반 homeViewFilterSchema, apiDayOfWeekSchema, homeViewDaySchema, homeViewDataSchematodoSchema 등이 추가/전환되어 API 응답 및 Todo 타입이 스키마 추론 기반으로 바뀐다.
날짜/요일 유틸리티
home/_utils/date.ts, home/_utils/day-of-week.ts
getToday, addDays, isSameDate, buildDateRange, formatDateKey, parseDateKey, getApiDayOfWeek가 새로 추가된다.
mock 데이터 레이어
home/_mocks/home-view-mock.ts, home/_mocks/todo-mock.ts, home/_mocks/todo-order-mock.ts
getHomeViewMock, getTodoMocksByDate, patchTodoOrderMock이 추가되어 필터/기준일 기반 홈 뷰 데이터와 순서 변경 mock API를 제공한다.
뷰 모드/스크롤 훅
home/_hooks/useHomeViewMode.ts, home/_hooks/useHomeTodayScroll.ts
URL 쿼리 기반 useHomeViewMode(뷰 전환, 주 이동, 오늘 이동)와 useHomeTodayScrollRef/triggerScrollToToday가 추가된다.
날짜별 투두 상태 및 정렬
home/_hooks/useHomeTodosByDate.ts, home/_utils/todo-order.ts, home/_utils/home-view.ts
useHomeTodosByDate가 완료/타이머/서브태스크 토글 및 재정렬(reorderTodos, patchTodoOrderMock 롤백 포함) 핸들러를 제공하고, reorderDaysTodayFirst가 오늘 날짜를 앞에 배치한다.
드래그 정렬 프로바이더
providers/dnd/DndSortableListProvider.tsx
dnd-kit 기반 DndContext/SortableContext를 구성해 드래그 종료 시 인덱스 계산 후 onReorder를 호출한다.
홈 컨테이너/카드 배선
home/_containers/HomeTodoContainer.tsx, home/_containers/HomeDayHeaderContainer.tsx, home/_containers/HomeHeaderContainer.tsx, home/_components/HomeTodoCard.tsx, home/page.tsx, home/_utils/todo-time.ts
날짜 컬럼 기반 렌더링, sortable 카드, 우선순위 번역 라벨 계산, AsyncBoundary 분리 및 오늘 이동 스크롤 연동이 구현된다.
요일/우선순위 i18n 및 패키지 의존성
messages/en.json, messages/ko.json, utils/get-day-of-week-key.ts, package.json
weekday/priority 메시지 키가 API enum 값(SUN~SAT, urgent/high/medium)으로 변경되고, @dnd-kit/*, zustand 의존성이 추가된다.

타임 사이드바 상태의 Zustand 전환

Layer / File(s) Summary
사이드바 스토어 및 연결
stores/time-sidebar/useTimeSidebarStore.ts, components/layout/sidebar/time/TimeSidebar.tsx, _containers/WithTimeSidebarContainer.tsx
useTimeSidebarStore(isOpen, activeTab, toggleOpen, setActiveTab, openTimerPanel)가 추가되고, TimeSidebar/WithTimeSidebarContainer가 로컬 useState 대신 이 스토어를 사용하도록 변경된다.

AddTaskButton weekly variant 제거

Layer / File(s) Summary
컴포넌트/스토리 variant 정리
packages/timo-design-system/.../AddTaskButton.tsx, AddTaskButton.stories.tsx
weekly variant와 관련 스타일/스토리가 제거되고 default/big 중심으로 단순화된다.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant HomeHeaderContainer
  participant useHomeViewMode
  participant HomeTodoContainer
  participant useHomeTodosByDate
  participant DndSortableListProvider

  HomeHeaderContainer->>useHomeViewMode: setViewMode(basic/week)
  useHomeViewMode-->>HomeTodoContainer: isWeekView, referenceDate
  HomeTodoContainer->>useHomeTodosByDate: apiDays 전달
  useHomeTodosByDate-->>HomeTodoContainer: todosByDate, handleReorderTodo
  DndSortableListProvider->>useHomeTodosByDate: handleReorderTodo(fromIndex, toIndex)
  useHomeTodosByDate->>useHomeTodosByDate: reorderTodos 적용 후 patchTodoOrderMock 호출, 실패 시 롤백
Loading
sequenceDiagram
  participant WithTimeSidebarContainer
  participant useTimeSidebarStore
  participant TimeSidebar

  WithTimeSidebarContainer->>useTimeSidebarStore: isOpen 구독
  TimeSidebar->>useTimeSidebarStore: toggleOpen() 호출
  useTimeSidebarStore-->>WithTimeSidebarContainer: isOpen 갱신
  useTimeSidebarStore-->>TimeSidebar: activeTab 갱신
Loading

Possibly related PRs

  • Team-Timo/Timo-client#49: HomeTodoCardPriorityIcon에 우선순위 라벨을 전달하는 변경이 해당 PR에서 추가된 PriorityIcon 컴포넌트와 직접 맞물린다.
  • Team-Timo/Timo-client#111: WithTimeSidebarContainerisOpen 토글 로직을 로컬 상태에서 스토어 구독으로 바꾼 변경이 동일 파일의 기존 로컬 상태 처리 흐름을 대체한다.
  • Team-Timo/Timo-client#103: TimeSidebar의 탭 상태를 useTimeSidebarStore로 전환한 변경이 해당 PR의 Timebox/Timer 사이드바 상태 관리 코드와 직접 연결된다.

Suggested labels: ♠️ 정민

Suggested reviewers: yumin-kim2, ehye1, jjangminii

Poem

토끼가 콩콩, 날짜 칸을 세어보니
일곱 칸 주간 뷰가 활짝 열렸네
드래그로 투두를 사샤삭 옮기고
사이드바는 스토어 품에 쏙 안기고
🐰 오늘 버튼 누르면 스르륵 스크롤!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning 타이머 사이드바 연동과 QueryProvider 경로 변경은 #121의 홈 뷰 퍼블리싱 범위를 벗어난 추가 변경입니다. 타이머 연동/레이아웃 리팩터링은 별도 PR로 분리하거나 #121 범위 포함 근거를 추가해 주세요.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed home-view mock, 7일 뷰 레이아웃, AddTaskButton 폭, 요일/priority i18n 키 정리 등 #121 요구사항이 반영되었습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 홈 화면 7일 뷰, 투두 드래그 정렬, 타이머 연동이라는 PR의 핵심 변경을 정확하고 간결하게 요약합니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_types/home-view-type.ts (1)

18-18: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

date 필드에 날짜 형식 검증 추가를 권장합니다.

homeViewDaySchemadate 필드가 z.string()으로 정의되어 있어 "yyyy-MM-dd" 형식을 보장하지 않습니다. 현재 mock 단계에서는 문제가 없으나, 실제 API 연동 시 잘못된 형식의 날짜가 들어올 수 있습니다. z.iso.date() 또는 정규식 패턴 검증을 추가하는 것을 권장합니다.

♻️ 제안: 날짜 형식 검증 추가
 export const homeViewDaySchema = z.object({
-  date: z.string(),
+  date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "date must be yyyy-MM-dd format"),
   dayOfWeek: apiDayOfWeekSchema,
🤖 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/app/`[locale]/(main)/(with-time-sidebar)/home/_types/home-view-type.ts
at line 18, The homeViewDaySchema in home-view-type.ts currently uses z.string()
for date, so it does not enforce the expected yyyy-MM-dd format. Update the date
field validation in homeViewDaySchema to use a date-specific check such as
z.iso.date() or an equivalent regex-based refinement, while keeping the existing
schema shape intact for the home view types.
apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_mocks/home-view-mock.ts (1)

36-48: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

getToday() 호출을 반복문 밖으로 이동하는 것을 권장합니다.

dates.map 콜백 내에서 getToday()가 매 반복마다 호출됩니다. 자정 직후 실행 시 반복 중 날짜가 넘어가는 극단적 엣지 케이스에서 isToday 판정이 일관되지 않을 수 있습니다. getToday()를 반복문 밖에서 한 번만 호출하여 재사용하는 것이 더 안전합니다.

♻️ 제안: getToday()를 반복문 밖으로 이동
+ const today = getToday();
+
  const days: HomeViewDay[] = dates.map((date) => {
    const todos = getTodoMocksByDate(date);

    return {
      date: formatDateKey(date),
      dayOfWeek: getApiDayOfWeek(date),
      isHoliday: false,
-     isToday: isSameDate(date, getToday()),
+     isToday: isSameDate(date, today),
      totalCount: todos.length,
      completedCount: todos.filter((todo) => todo.completed).length,
      todos,
    };
  });
🤖 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/app/`[locale]/(main)/(with-time-sidebar)/home/_mocks/home-view-mock.ts
around lines 36 - 48, The `dates.map` callback in `home-view-mock.ts` calls
`getToday()` for every item, so move that call outside the loop in the
`HomeViewDay` निर्माण logic and reuse a single captured “today” value when
computing `isToday`. Keep the rest of the mapping unchanged, and update the
`isToday` check to compare each `date` against the shared value rather than
calling `getToday()` repeatedly.
apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_utils/day-of-week.ts (1)

10-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

as ApiDayOfWeek 타입 단언 대신 타입 안전성을 강화하는 것을 권장합니다.

getDayOfWeekKey(date)의 반환 타입이 ApiDayOfWeek와 일치하도록 getDayOfWeekKey 자체의 반환 타입을 조정하거나, 런타임 검증을 추가하는 것이 더 안전합니다. 현재 as 단언은 getDayOfWeekKey가 예상과 다른 값을 반환할 경우 타입 오류를 발생시키지 않습니다.

♻️ 제안: 타입 단언 제거 방안
- export const getApiDayOfWeek = (date: Date): ApiDayOfWeek =>
-   getDayOfWeekKey(date) as ApiDayOfWeek;
+ // getDayOfWeekKey의 반환 타입을 ApiDayOfWeek와 일치시키거나
+ // apiDayOfWeekSchema로 런타임 파싱 추가
+ export const getApiDayOfWeek = (date: Date): ApiDayOfWeek =>
+   apiDayOfWeekSchema.parse(getDayOfWeekKey(date));
🤖 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/app/`[locale]/(main)/(with-time-sidebar)/home/_utils/day-of-week.ts
around lines 10 - 11, The `getApiDayOfWeek` helper is relying on an unsafe `as
ApiDayOfWeek` cast, so update `getDayOfWeekKey` or `getApiDayOfWeek` to make the
return type type-safe instead of asserting it. Prefer aligning
`getDayOfWeekKey`’s return type with `ApiDayOfWeek`, or add a runtime check in
`getApiDayOfWeek` before returning the value, so the conversion is validated
without a blind cast.
apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/HomeTodoCard.tsx (1)

76-80: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

완료된 todo 카드의 touchAction: "none" 불필요 적용 및 전체 카드 드래그 핸들.

useSortable({ disabled: isCompleted })로 완료된 todo는 드래그가 비활성화되지만, sortableStyletouchAction: "none"은 항상 적용됩니다. 완료된 카드에서는 불필요하게 터치 스크롤이 차단됩니다. 또한 {...listeners}가 전체 <article>에 spread되어 있어 터치 환경에서 카드 영역 세로 스크롤이 어려울 수 있습니다.

♻️ 제안: disabled 시 touchAction 조건부 적용
   const sortableStyle = {
     transform: CSS.Transform.toString(transform),
     transition,
-    touchAction: "none",
+    ...(isCompleted ? {} : { touchAction: "none" }),
   };

Also applies to: 122-125

🤖 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/app/`[locale]/(main)/(with-time-sidebar)/home/_components/HomeTodoCard.tsx
around lines 76 - 80, In HomeTodoCard’s sortable styling and drag setup,
`touchAction: "none"` is being applied even when `useSortable({ disabled:
isCompleted })` disables dragging, and `{...listeners}` on the full `<article>`
makes the whole card act like a drag handle. Update `sortableStyle` so
`touchAction` is applied conditionally only when dragging is enabled, and move
the drag listeners to a dedicated handle area instead of the entire card so
completed cards and touch scrolling behave normally.
🤖 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.

Nitpick comments:
In
`@apps/timo-web/app/`[locale]/(main)/(with-time-sidebar)/home/_components/HomeTodoCard.tsx:
- Around line 76-80: In HomeTodoCard’s sortable styling and drag setup,
`touchAction: "none"` is being applied even when `useSortable({ disabled:
isCompleted })` disables dragging, and `{...listeners}` on the full `<article>`
makes the whole card act like a drag handle. Update `sortableStyle` so
`touchAction` is applied conditionally only when dragging is enabled, and move
the drag listeners to a dedicated handle area instead of the entire card so
completed cards and touch scrolling behave normally.

In
`@apps/timo-web/app/`[locale]/(main)/(with-time-sidebar)/home/_mocks/home-view-mock.ts:
- Around line 36-48: The `dates.map` callback in `home-view-mock.ts` calls
`getToday()` for every item, so move that call outside the loop in the
`HomeViewDay` निर्माण logic and reuse a single captured “today” value when
computing `isToday`. Keep the rest of the mapping unchanged, and update the
`isToday` check to compare each `date` against the shared value rather than
calling `getToday()` repeatedly.

In
`@apps/timo-web/app/`[locale]/(main)/(with-time-sidebar)/home/_types/home-view-type.ts:
- Line 18: The homeViewDaySchema in home-view-type.ts currently uses z.string()
for date, so it does not enforce the expected yyyy-MM-dd format. Update the date
field validation in homeViewDaySchema to use a date-specific check such as
z.iso.date() or an equivalent regex-based refinement, while keeping the existing
schema shape intact for the home view types.

In
`@apps/timo-web/app/`[locale]/(main)/(with-time-sidebar)/home/_utils/day-of-week.ts:
- Around line 10-11: The `getApiDayOfWeek` helper is relying on an unsafe `as
ApiDayOfWeek` cast, so update `getDayOfWeekKey` or `getApiDayOfWeek` to make the
return type type-safe instead of asserting it. Prefer aligning
`getDayOfWeekKey`’s return type with `ApiDayOfWeek`, or add a runtime check in
`getApiDayOfWeek` before returning the value, so the conversion is validated
without a blind cast.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d213e673-54b6-4770-b92c-de4fcae56de0

📥 Commits

Reviewing files that changed from the base of the PR and between 47e91db and 2f68202.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (33)
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/_containers/WithTimeSidebarContainer.tsx
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/HomeTodoCard.tsx
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeDayHeaderContainer.tsx
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeHeaderContainer.tsx
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsx
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/toast/TagLimitToastContainer.tsx
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/toast/TodoLimitToastContainer.tsx
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/useHomeTodayScroll.ts
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/useHomeTodosByDate.ts
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/useHomeViewMode.ts
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_mocks/home-view-mock.ts
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_mocks/todo-mock.ts
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_mocks/todo-order-mock.ts
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_types/home-view-type.ts
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_types/todo-type.ts
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_utils/date.ts
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_utils/day-of-week.ts
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_utils/home-view.ts
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_utils/todo-order.ts
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_utils/todo-time.ts
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/page.tsx
  • apps/timo-web/app/[locale]/layout.tsx
  • apps/timo-web/components/layout/sidebar/time/TimeSidebar.tsx
  • apps/timo-web/messages/en.json
  • apps/timo-web/messages/ko.json
  • apps/timo-web/package.json
  • apps/timo-web/providers/dnd/DndSortableListProvider.tsx
  • apps/timo-web/providers/query/QueryProvider.tsx
  • apps/timo-web/stores/.gitkeep
  • apps/timo-web/stores/time-sidebar/useTimeSidebarStore.ts
  • apps/timo-web/utils/get-day-of-week-key.ts
  • packages/timo-design-system/src/components/button/add-task-button/AddTaskButton.stories.tsx
  • packages/timo-design-system/src/components/button/add-task-button/AddTaskButton.tsx

@jjangminii jjangminii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleReorderTodo에서 드롭 즉시 로컬 상태를 업데이트하고 API 실패 시 이전 상태로 롤백하는 낙관적 업데이트 패턴이 깔끔하게 잘 구현해주셨네요 👍

DndSortableListProvider로 dnd-kit 설정을 분리해서 HomeTodoContainer가 드래그 세부사항을 몰라도 되는 구조도 좋아요-! 나중에 다른 도메인에서도 재사용하기 좋을 것 같아요.
투두 카드에서 타임 사이드바를 열어야 하는데 두 컴포넌트가 완전히 다른 트리에 있어서 props로 넘길 방법이 없는 상황이라 Zustand 도입 판단도 적절한 것 같아요! 제가 전역상태관리는 처음이라 열심히 공부해볼게요..

코멘트만 확인해주세요-! 고생하셨습니다~

Comment on lines 121 to 130
<article
ref={setNodeRef}
style={sortableStyle}
{...attributes}
{...listeners}
className={cn(
"border-timo-gray-500 flex size-full flex-col items-start gap-2 overflow-hidden rounded-[4px] border border-solid px-3.5 py-3",
"border-timo-gray-500 flex w-full shrink-0 flex-col items-start gap-2 overflow-hidden rounded-[4px] border border-solid px-3.5 py-3",
isCompleted ? "bg-timo-gray-200" : "bg-white",
)}
>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

체크박스나 재생 버튼 클릭할 때도 드래그가 시작될 수 있어보여요. distance: 8 제약으로 어느 정도 방어는 되지만 완전하지 않다고 생각이 들어요. 별도 드래그 핸들 아이콘을 만들고 거기에만 {...listeners}를 붙이는 게 안전할것 같은데 어떤가요?

}

const query = params.toString();
router.replace(query ? `${pathname}?${query}` : pathname);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

뷰 전환이 히스토리에 안 남아서 브라우저 뒤로가기를 누르면 이전 뷰가 아닌 이전 페이지로 이동하는데 의도한 UX가 맞나요?
혹시 뒤로가기로 이전 뷰로 돌아올 수 있길 원한다면 router.push로 바꿔야 할 것 같아요-!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋네요! 뷰 전환은 사용자가 명시적으로 선택하는 부분이라 뒤로가기 클릭 시 복원되는 걸 기대할 것 같아요. 반영하겠습니다!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

생각해 보니 query 형태로 URL이 명시적으로 토글 시 쿼리가 전환되는 거라 router.push는 필요 없을 것 같긴 합니다. 이건 조금 더 고민해 보고 반영할게요!


export interface DndSortableListProviderProps {
dndId: string;
itemIds: number[];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금 당장 문제가 되는 건 아닌데, dnd-kit의 UniqueIdentifier는 string | number를 모두 지원하는데 DndSortableListProvider의 itemIds가 number[]로 고정되어 있어요. 나중에 다른 도메인에서 string id를 쓰게 되면 이 Provider를 재사용하지 못하고 새로 만들어야 할 수 있어서요. 지금은 todoId: number라 문제없지만 한번 고려해보시면 좋을 것 같아요 😊

Comment on lines +3 to +8
/**
* 기본 뷰(가로 스크롤)에서 오늘 날짜가 맨 앞에 오도록 days 배열을 회전시킨다.
* 오늘 이전의 날짜들은 순서를 유지한 채 배열 끝으로 옮겨진다.
* @param days - 재정렬할 날짜 목록
* @returns 오늘부터 시작하도록 재정렬된 날짜 목록 (오늘이 없으면 원본 그대로 반환)
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSDoc도 꼼꼼하게 챙겨주셨네요.. 👍 저도 유틸에는 습관을 들여볼게요.

@ehye1 ehye1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Zustand 써본 적 없는데 이번 PR보면서 서로 다른 컴포넌트에서 같은 상태를 공유하는 흐름을 코드로 많이 배웠습니다! 드래그앤드롭 라이브러리로 dnd-kit도 처음 알게되었는데 자연스럽게 드래그가 돼서 좋아보이네요,,
두 가지 뷰 함께 구현하시느라 넘넘 고생많으셧어요~~~ 👍🏻👍🏻

<Checkbox
checked={isCompleted}
onChange={onToggleCompleted}
disabled={isCompleted}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo 완료되면 체크박스도 잠기는 걸로 보이는데 화면설계서에서 [Home-Default 4-4]에 완료 투두에서 체크 박스는 수정할 수가 있어서 확인부탁드립니다!
(TodayTodoCard처럼 완료되어도 hover시에 체크박스 수정가능한걸로 알고있습니다.)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

체크박스 수정 시 완료 토글 API를 호출하고, 재정렬된 데이터를 받아와야 해서 지금 프론트 로직에서 추가할 건 없어보입니다. 추후에 수정해 둘게요~!!

Comment on lines +44 to +58
const handleTogglePlay = (dateKey: string, todoId: number) => {
// TODO: API
const willRun =
todosByDate[dateKey]?.find((todo) => todo.todoId === todoId)
?.timerStatus !== "RUNNING";

updateTodo(dateKey, todoId, (todo) => ({
...todo,
timerStatus: todo.timerStatus === "RUNNING" ? "STOPPED" : "RUNNING",
}));

if (willRun) {
openTimerPanel();
}
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleTogglePlay가 클릭한 투두의 timerStatus만 토글해서 이미 RUNNING중인 다른 투두가 있어도 그대로 유지되어서 동시에 두 개가 실행이 가능한 것으로 보여요!
새로 재생 시작할 때 기존에 돌고 있던 투두를 STOPPED로 함께 전환하는 로직이 필요할 것 같습니다!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이것 또한 실제 API 연동 시 상태 관리를 실데이터를 통해 전환해야 해서 해당 부분 구현할 때 함께 구현해 둘게요!

Comment on lines +9 to +19
export const reorderDaysTodayFirst = (days: HomeViewDay[]): HomeViewDay[] => {
const todayIndex = days.findIndex((day) => day.isToday);

if (todayIndex === -1) {
return days;
}

const upcoming = days.slice(todayIndex);
const past = days.slice(0, todayIndex);
return [...upcoming, ...past];
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reorderDaysTodayFirst로 배열을 오늘 -> 미래 -> 과거 순으로 배치하고 있는데 이러면 스크롤 방향이 실제 날짜 흐름과 어긋나서 어색해보이는 것 같아요!
배열은 과거→오늘→미래 시간 순서 그대로 두고, 진입 시 "오늘" 카드 위치로 스크롤 포커스만 옮기는 방식(오늘 카드에 ref 걸고 scrollIntoView)이 더 직관적일 것 같은데 기획의도가 그렇다면 넘어가겠습니다,,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API 연동 전 목데이터를 위한 함수라서 이대로 두고 실제 연동 시에 구현해 두면 될 것 같습니다!

@kimminna kimminna merged commit 3bb25bc into develop Jul 10, 2026
44 checks passed
@kimminna kimminna deleted the feat/web/121-home-view-publishing branch July 10, 2026 06:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 새로운 기능(기능성) 구현 ⌚ Timo-Design-system Timo 디자인 시스템 ⏰ Timo-web Timo 웹 서비스 ♦️ 민아 민아상

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 홈 화면 뷰 퍼블리싱

3 participants