Skip to content

feat : todo CRUD 개선#55

Closed
moo1677 wants to merge 7 commits into
devfrom
feature/home-page
Closed

feat : todo CRUD 개선#55
moo1677 wants to merge 7 commits into
devfrom
feature/home-page

Conversation

@moo1677
Copy link
Copy Markdown
Contributor

@moo1677 moo1677 commented Jan 28, 2026

제목 규칙 (Conventional Commits)

  • feat:, fix:, refactor:, perf:, docs:, test:, chore: 중 택1
  • 예) feat: 팀 추천 페이지 필터 추가, fix: 로그인 폼 유효성 버그 수정

1) 작업한 이슈번호

31

2) 변경 요약 (What & Why)

  • 무엇을 변경했는지:
  • 변경했는지(문제/목표):

3) 스크린샷/동영상 (UI 변경 시)

전/후 비교, 반응형(모바일/데스크톱) 캡쳐

  • Before:
  • After:
image

4) 상세 변경사항

  • 라우팅/페이지:
  • 컴포넌트:
  • 상태관리:
  • API 호출:
  • 스타일:
  • 기타:

5) 참고사항

Summary by CodeRabbit

  • 새로운 기능

    • 할일 항목 편집 및 삭제 기능 추가(인라인 편집: Enter 저장, Esc 취소)
    • 할일 추가 UI 개선(추가 입력 토글, 플러스 버튼)
    • 프로필에 동적 진행률 표시 추가
  • 버그 수정 / 안정성

    • 할일 로드/조작 시 로딩·오류 처리 및 낙관적 업데이트로 UI 반영 개선
    • 날짜 기준 처리 보정으로 일별 할일 정확성 향상
  • 리팩터

    • 할일 관리 및 관련 UI 구조 재정비로 구성 단순화 및 재사용성 향상

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 28, 2026

📝 Walkthrough

Walkthrough

페이지에서 로컬 todo 상태 및 직접 로직을 제거하고, uid 기반의 useTodos 훅과 TodoContainer로 상태·CRUD를 이동했습니다. CheckItem에 편집/삭제가 추가되고 todoService에 dateKey/createdAt 필드 및 삭제·수정 API가 도입되었습니다.

Changes

Cohort / File(s) 변경 요약
페이지
app/(with-sidebar)/(home)/page.tsx
local todo 상태 제거; useTodos(uid)로 대체; uid 기반 프로필/데이터 로드로 변경; BottomSection/TodoContainer에 uid 및 todos props 전파
훅 & 서비스
hooks/useTodos.ts, services/home/todoService.service.ts, lib/date.ts
새로운 useTodos(uid) 훅 추가(낙관적 업데이트, 로딩/에러 처리, CRUD 핸들러); todoService에 fetchTodos(uid, dateKey), addTodo, deleteTodo, updateTodoText 등 시그니처·모델(생성시간→createdAt, dateKey) 변경; KST 날짜 키 dateKeyKST 추가
컨테이너/하위 컴포넌트
components/home/TodoContainer.tsx, components/home/BottomSection.tsx
TodoContainer 추가(로딩/오류 처리, Card + CheckList 조합); BottomSection이 uid와 todos 관련 핸들러(토글/추가/삭제/편집)를 받도록 변경 및 TodoContainer 사용으로 단순화
체크리스트 아이템
components/home/CheckList.tsx, components/home/CheckItem.tsx
CheckList에 add UI(draft, showAdd) 및 onAdd/onRemoveTodo/onEditTodoText 추가; CheckItem에 인라인 편집(isEditing, draft), onEdit/onDelete 핸들러 및 메뉴 추가, 접근성 키바인딩 개선
UI 레이아웃
components/home/Card.tsx, components/home/ProfileSection.tsx
Card에 rightSlot prop 추가(헤더 우측 슬롯 레이아웃 지원); ProfileSection에 progressText prop 추가하여 동적 진행도 반영

Sequence Diagram

sequenceDiagram
    participant Page as "페이지 (page.tsx)"
    participant TodoContainer as "TodoContainer"
    participant useTodos as "useTodos 훅"
    participant Service as "todoService"
    participant CheckList as "CheckList"
    participant CheckItem as "CheckItem"

    Page->>TodoContainer: uid 전달
    activate TodoContainer
    TodoContainer->>useTodos: useTodos(uid) 호출
    activate useTodos

    useTodos->>Service: fetchTodos(uid, dateKey)
    activate Service
    Service-->>useTodos: todos 반환
    deactivate Service

    useTodos-->>TodoContainer: todos, loading, error, 핸들러들
    deactivate useTodos

    TodoContainer->>CheckList: items + 콜백 전달
    activate CheckList
    CheckList->>CheckItem: 각 항목 렌더링
    activate CheckItem

    alt 사용자 토글
        CheckItem->>useTodos: onToggleTodo(id, checked)
    else 사용자 편집
        CheckItem->>useTodos: onEditTodoText(id, text)
    else 사용자 삭제
        CheckItem->>useTodos: onRemoveTodo(id)
    end

    useTodos->>Service: 낙관적 업데이트 후 서버 동기화 (add/toggle/delete/update)
    activate Service
    Service-->>useTodos: 결과/오류
    deactivate Service

    useTodos-->>TodoContainer: 상태 갱신
    deactivate CheckItem
    deactivate CheckList
    deactivate TodoContainer
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50분

Possibly related PRs

Poem

🐰 훅으로 모은 오늘의 할 일,
펜 대신 키보드로 고쳐 쓰네.
추가·편집·삭제에 깡충깡충,
낙관적 업데이트로 마음은 가뿐히.
당근 한 입, 코드 한 줄 더! 🥕✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning PR 설명이 템플릿 구조는 따르지만 필수 섹션인 '무엇을 변경했는지', '왜 변경했는지', '상세 변경사항(라우팅/페이지, 컴포넌트, 상태관리, API 호출 등)' 등이 거의 비워져 있습니다. PR 설명을 작성할 때 다음을 명시해주세요: (1) 구체적으로 변경한 내용, (2) 변경 이유, (3) 영향을 받은 컴포넌트와 상태관리 방식, (4) 수정된 API 호출 방식 등의 상세 변경사항을 각 섹션에 채워주시기 바랍니다.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경사항을 명확하게 설명하고 있습니다. 'todo CRUD 개선'은 할 일 기능의 생성, 읽기, 수정, 삭제 작업 개선을 직접적으로 나타냅니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In `@app/`(with-sidebar)/(home)/page.tsx:
- Around line 17-22: useEffect currently calls getProfile(currentUser.uid) in an
unguarded async IIFE which can produce unhandled promise rejections; wrap the
await getProfile(...) call in a try/catch inside the IIFE (or extract to an
async function) and on error call your logger (or console.error) and set an
error/loading state so the UI can show a failure state; ensure you still call
setProfile(userProfile) only on success and handle cancellation if currentUser
changes before the fetch completes.

In `@components/home/CheckItem.tsx`:
- Around line 51-107: The outer <button> contains an <input>, which breaks HTML
semantics and causes accessibility issues; replace the <button> with a
non-button container using role="button" (or role="checkbox" if you prefer
semantic state) and preserve the original onToggle behavior by adding
tabIndex={0}, an onClick that calls onToggle, and an onKeyDown that triggers
onToggle when the user presses Enter or Space; keep existing className and the
inner input as-is (it should still call e.stopPropagation() onClick) and ensure
to add aria-pressed={checked} or aria-checked={checked} on the container and
maintain handlers/refs to submitEdit, setDraft, setIsEditing, isEditing, and
text so behavior is unchanged.

In `@components/home/CheckList.tsx`:
- Around line 56-61: Replace the non-semantic clickable div used for the "추가"
action with a proper <button> element to improve accessibility; locate the
element in CheckList.tsx (the element with className "text-primary flex w-10
items-center text-sm font-bold" and onClick={handleSubmit}) and change it to a
<button type="button"> preserving the className and onClick handler (and add an
appropriate aria-label if the visible text isn't sufficiently descriptive).

In `@hooks/useTodos.ts`:
- Around line 34-66: createTodo uses optimistic UI but currently rolls back the
temp item when fetchTodos fails even if addTodo succeeded; change the flow so
addTodo returns the created todo id or full item (update addTodo to return this
if necessary), after await addTodo(uid, trimmed) replace the temp item (tempId)
in setTodos with the real item using the returned id/item instead of calling
fetchTodos for full sync, and only call fetchTodos as a best-effort update that
does not remove the optimistic item on failure (i.e., on fetchTodos failure
log/set an error but keep/replace the temp item using the addTodo result);
reference symbols: createTodo, addTodo, fetchTodos, tempId, setTodos.
- Around line 17-25: todayKey is computed once via useMemo and becomes stale
after midnight, so change logic to compute dateKey at action time: remove
reliance on the memoized todayKey inside loadTodos and any createTodo/addTodo
flows and call dateKeyKST(new Date()) where you currently pass todayKey (e.g.,
inside loadTodos and when creating a todo) or implement a midnight refresh that
updates todayKey; update references to todayKey (from useMemo) and functions
loadTodos, fetchTodos, createTodo/addTodo to use the fresh dateKey value so
fetchTodos filters by the current date.

In `@services/home/todoService.service.ts`:
- Around line 32-36: The query using todosCollectionPath with where('dateKey',
'==', dateKey) and orderBy('createdAt', 'asc') requires a Firestore composite
index; add a composite index for dateKey (Ascending) + createdAt (Ascending) on
the users/{uid}/todos collection in the Firebase Console or add the
corresponding entry to firestore.indexes.json so the query in the q constant
succeeds without runtime index errors.
🧹 Nitpick comments (9)
lib/date.ts (1)

1-3: dateKeyKST 중복 정의는 공통 유틸로 통합하는 편이 좋습니다.

services/heatmap/dailyStat.service.ts에도 동일한 로직이 있어 수정 시 드리프트 위험이 있습니다. 해당 파일에서도 이 유틸을 import해 쓰도록 정리하는 것을 권장합니다.

components/home/TodoContainer.tsx (1)

4-28: 초기 로딩이 훅과 컴포넌트에서 중복 호출됩니다.

useTodos 내부에서 이미 uid 변경 시 loadTodos가 호출되므로 여기의 useEffect는 중복 요청이 됩니다. 한 곳으로만 두는 편이 효율적입니다.

💡 중복 호출 제거 예시
-import { useEffect, useState } from 'react';
+import { useState } from 'react';-    loadTodos,-  useEffect(() => {
-    loadTodos();
-  }, [loadTodos]);
components/home/BottomSection.tsx (1)

3-51: 주석 처리된 더미/구현 코드는 정리해도 좋겠습니다.

불필요한 주석 블록이 많아 가독성이 떨어집니다. 히스토리가 필요 없다면 삭제를 권장합니다.

services/home/todoService.service.ts (2)

39-47: 중복된 doc.data() 호출

Line 40에서 data 변수에 이미 doc.data()를 저장했는데, Line 45에서 다시 doc.data()를 호출하고 있습니다. 기존 data 변수를 사용하세요.

♻️ 수정 제안
  return snapshot.docs.map((doc) => {
    const data = doc.data();
    return {
      id: doc.id,
      text: data.text,
      isChecked: !!data.isChecked,
-      createdAt: doc.data().createdAt?.toDate?.() ?? new Date(),
+      createdAt: data.createdAt?.toDate?.() ?? new Date(),
    };
  });

51-58: addTodo에서 생성된 문서 ID 반환 고려

낙관적 업데이트(optimistic update)를 위해 addDoc이 반환하는 DocumentReference에서 ID를 반환하면, 클라이언트에서 임시 ID 대신 실제 ID를 바로 사용할 수 있습니다.

♻️ 수정 제안
-export const addTodo = async (uid: string, text: string) => {
-  await addDoc(collection(db, 'users', uid, 'todos'), {
+export const addTodo = async (uid: string, text: string): Promise<string> => {
+  const docRef = await addDoc(collection(db, 'users', uid, 'todos'), {
     text,
     isChecked: false,
     dateKey: dateKeyKST(),
     createdAt: Timestamp.now(),
   });
+  return docRef.id;
 };
components/home/CheckList.tsx (4)

6-6: 사용되지 않는 import

HiPlus가 import 되었지만 이 컴포넌트에서 사용되지 않습니다. 사용되지 않는 import는 제거해주세요.

♻️ 수정 제안
-import { HiPlus } from 'react-icons/hi';

11-14: 사용되지 않는 props 정리 필요

emptyTextonCancelAdd가 타입에 정의되어 있지만 컴포넌트에서 destructure되지 않았습니다. 사용하지 않는다면 제거하고, 향후 사용 예정이라면 TODO 주석을 추가해주세요.


33-39: 중복된 setDraft('') 호출

Line 34와 Line 38에서 setDraft('')가 중복 호출됩니다. Line 34의 조건부 초기화는 불필요합니다.

♻️ 수정 제안
  const handleSubmit = () => {
-   if (showAdd) setDraft('');
    const text = draft.trim();
-   if (!text) return;
+   if (!text) {
+     setDraft('');
+     return;
+   }
    onAdd?.(text);
    setDraft('');
  };

50-54: 중복 조건 검사 제거

Line 51에서 e.key !== 'Enter'일 때 이미 return하므로, Line 53의 e.key === 'Enter' 검사는 불필요합니다.

♻️ 수정 제안
            onKeyDown={(e) => {
              if (e.key !== 'Enter') return;
              if (e.nativeEvent.isComposing) return;
-              if (e.key === 'Enter') handleSubmit();
+              handleSubmit();
            }}

Comment thread app/(with-sidebar)/(home)/page.tsx
Comment thread components/home/CheckItem.tsx Outdated
Comment thread components/home/CheckList.tsx Outdated
Comment thread hooks/useTodos.ts
Comment on lines +17 to +25
const todayKey = useMemo(() => dateKeyKST(new Date()), []);

const loadTodos = useCallback(async () => {
if (!uid) return;
try {
setLoading(true);
setError(null);
const data = await fetchTodos(uid, todayKey);
setTodos(data);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, find the useTodos.ts file
fd "useTodos.ts" --type f

Repository: DeveloperBlog-Devflow/devflow

Length of output: 90


🏁 Script executed:

# Get the file size first
wc -l hooks/useTodos.ts

Repository: DeveloperBlog-Devflow/devflow

Length of output: 94


🏁 Script executed:

# Read the entire useTodos.ts file
cat -n hooks/useTodos.ts

Repository: DeveloperBlog-Devflow/devflow

Length of output: 5268


🏁 Script executed:

# Find the dateKeyKST function
fd "date.ts" "date.js" --type f | head -20

Repository: DeveloperBlog-Devflow/devflow

Length of output: 167


🏁 Script executed:

# Search for dateKeyKST function definition
rg "dateKeyKST" --type ts --type js -B 2 -A 5

Repository: DeveloperBlog-Devflow/devflow

Length of output: 3741


🏁 Script executed:

# Check addTodo implementation to verify it uses current date, not todayKey
rg "export.*addTodo" -A 20 services/home/todoService.service.ts | head -30

Repository: DeveloperBlog-Devflow/devflow

Length of output: 571


🏁 Script executed:

# Check if there's any useEffect that refreshes or resets todayKey based on time
rg "useEffect" hooks/useTodos.ts -A 5

# Also check if there's any visibility/focus handling
rg "visibilitychange\|focus\|blur" hooks/useTodos.ts

Repository: DeveloperBlog-Devflow/devflow

Length of output: 299


🏁 Script executed:

# Check if fetchTodos filters by dateKey to understand the impact
rg "fetchTodos" services/home/todoService.service.ts -B 2 -A 10

Repository: DeveloperBlog-Devflow/devflow

Length of output: 406


todayKey가 컴포넌트 마운트 시점에 고정되어 있어 자정 이후 날짜 변경 시 이전 키를 계속 사용합니다.

장시간 열린 탭에서 자정을 지나 새 날짜가 되었을 때, todayKey는 여전히 마운트 당시의 이전 날짜를 유지합니다. fetchTodosdateKey 값으로 필터링하기 때문에 이전 날짜의 할 일만 조회됩니다. 특히 createTodo에서 addTodo는 현재 날짜의 dateKey로 항목을 생성하지만, 이후 fetchTodos는 스테일된 todayKey로 조회하므로 새로 추가한 항목이 결과에 포함되지 않아 UI에서 사라지는 문제가 발생합니다.

액션 실행 시점에 dateKeyKST(new Date())를 계산하도록 변경하거나, 자정 갱신 로직을 추가해주세요.

🤖 Prompt for AI Agents
In `@hooks/useTodos.ts` around lines 17 - 25, todayKey is computed once via
useMemo and becomes stale after midnight, so change logic to compute dateKey at
action time: remove reliance on the memoized todayKey inside loadTodos and any
createTodo/addTodo flows and call dateKeyKST(new Date()) where you currently
pass todayKey (e.g., inside loadTodos and when creating a todo) or implement a
midnight refresh that updates todayKey; update references to todayKey (from
useMemo) and functions loadTodos, fetchTodos, createTodo/addTodo to use the
fresh dateKey value so fetchTodos filters by the current date.

Comment thread hooks/useTodos.ts Outdated
Comment on lines +32 to +36
const q = query(
todosCollectionPath,
where('dateKey', '==', dateKey),
orderBy('createdAt', 'asc')
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Firestore composite index required where clause orderBy different field

💡 Result:

In Firestore, a query needs a composite index whenever it can’t be served by single‑field indexes—commonly when you combine a where(...) filter with an orderBy(...) on a different field (or multiple filters). Firestore will throw an error that includes a direct link to create the missing composite index. [1][2]

1) where(==) on one field + orderBy() on another

Example:

query(coll, where("status", "==", "open"), orderBy("createdAt", "desc"))

This typically requires a composite index like:

  • status (ASC) + createdAt (DESC) [1][2]

2) Inequality/range where + orderBy on a different field

If you use <, <=, >, >=, !=, not-in on field a, Firestore’s indexing rules are driven by the ORDER BY. The needed composite index follows the orderBy field order (left-to-right). [3]

So if you want results ordered by b, you generally must order by the range field first:

query(coll,
  where("a", ">", 10),
  orderBy("a"),
  orderBy("b")
)

…and the composite index must match that ordering, e.g.:

  • a (ASC) + b (ASC) (or DESC as used) [3]

3) Does the order of .where() calls matter?

Usually not for correctness/performance, but the index definition must match the query’s required field ordering (especially the orderBy sequence). Some developers also observe they may be prompted for different composite indexes when swapping filter-field order in certain compound queries. [2][4]

Practical tip: run the query once; click the Firestore error’s “create index” link—Firestore pre-fills the exact composite index it wants. [1]

Sources: Firebase indexing guide and index concepts [1][2], Firebase guidance on how ORDER BY drives index requirements [3], discussion of field/order sensitivity in composite indexes [4].

[1] (firebase.google.com)
[2] (firebase.google.com)
[3] (firebase.google.com)
[4] (stackoverflow.com)

Citations:


Firestore 복합 인덱스 필요

where('dateKey', '==', dateKey)orderBy('createdAt', 'asc')를 함께 사용하려면 Firestore에서 복합 인덱스가 필요합니다. 인덱스가 없으면 런타임에 에러가 발생합니다.

Firebase Console에서 users/{uid}/todos 컬렉션에 대해 dateKey (Ascending) + createdAt (Ascending) 복합 인덱스를 생성하거나, firestore.indexes.json에 추가해주세요.

🤖 Prompt for AI Agents
In `@services/home/todoService.service.ts` around lines 32 - 36, The query using
todosCollectionPath with where('dateKey', '==', dateKey) and
orderBy('createdAt', 'asc') requires a Firestore composite index; add a
composite index for dateKey (Ascending) + createdAt (Ascending) on the
users/{uid}/todos collection in the Firebase Console or add the corresponding
entry to firestore.indexes.json so the query in the q constant succeeds without
runtime index errors.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@components/home/BottomSection.tsx`:
- Around line 30-39: TodoContainer의 Props에 정의된 uid가 함수 매개변수(예:
TodoContainer(props) 또는 function TodoContainer({...}) 선언)에서 구조분해되지 않고 컴포넌트 본문에서
전혀 사용되지 않으므로 Props 정의와 구현을 일치시켜야 합니다; 해결책은 TodoContainer의 Props 타입에서 uid를 제거 if
unused, 또는 컴포넌트 선언(예: TodoContainer의 매개변수 목록)에서 uid를 구조분해하여 실제로 사용하도록 변경(예: 키,
사용자별 필터, API 호출에 전달 등)하고 사용처가 필요한 위치(렌더링 로직 또는 하위 컴포넌트에 전달)로 전달하는 것입니다.

In `@components/home/CheckList.tsx`:
- Around line 7-16: The CheckListProps lists emptyText and onCancelAdd but the
CheckList component never uses them; update the CheckList component to render
emptyText when items.length === 0 (falling back to a sensible default string)
and add a cancel control in the add-todo UI that calls the onCancelAdd prop;
specifically locate the CheckList component (props named emptyText and
onCancelAdd) and: 1) render the empty state using emptyText when there are no
items, and 2) add a cancel button next to the add/save controls that invokes
onCancelAdd() (ensuring showAdd toggling remains driven by the parent via
onCancelAdd).
- Around line 28-34: The bug is that handleSubmit clears draft before reading
it, so text is always empty; update handleSubmit (function handleSubmit,
variables draft, showAdd, onAdd, setDraft) to read and trim draft first (const
text = draft.trim()), return early if empty, then call onAdd?.(text) and only
afterwards clear the draft (setDraft('')); remove or relocate the initial if
(showAdd) setDraft('') so clearing doesn't happen before computing text.
🧹 Nitpick comments (9)
components/home/ProfileSection.tsx (1)

22-23: 불필요한 주석 처리된 코드를 제거해주세요.

더 이상 사용되지 않는 코드는 삭제하는 것이 코드 가독성 향상에 도움이 됩니다.

♻️ 수정 제안
-  // const goalText = totalTodos === 0 ? '0/0' : `${completedTodos}/${totalTodos}`;
-
services/home/todoService.service.ts (1)

24-25: 불완전한 주석을 제거하거나 완성해주세요.

// 0시 초기화 주석이 있지만 관련 구현이 없습니다. 의도한 기능이 있다면 구현하고, 아니라면 주석을 제거해주세요.

components/home/BottomSection.tsx (1)

41-47: 주석 처리된 코드 정리 또는 TODO 주석 추가를 고려해주세요.

"다가오는 일정" 기능을 위한 코드가 주석 처리되어 있습니다. 향후 구현 예정이라면 TODO 주석을 추가하고, 아니라면 제거하는 것이 좋습니다.

app/(with-sidebar)/(home)/page.tsx (1)

72-79: Non-null assertion(!) 대신 타입 가드 활용을 고려해주세요.

Line 55에서 !currentUser 체크 후 early return하므로 uid는 정의되어 있지만, TypeScript는 이를 추론하지 못합니다. uid! 대신 명시적인 타입 가드를 사용하면 더 안전합니다.

♻️ 수정 제안
-  if (!currentUser) {
+  if (!currentUser || !uid) {
     return (
       <div className="flex min-h-screen items-center justify-center">
         <p>로그인이 필요합니다.</p>
       </div>
     );
   }

이후 uid! 대신 uid를 바로 사용할 수 있습니다:

-        uid={uid!}
+        uid={uid}
components/home/CheckItem.tsx (1)

94-98: IME 조합 이벤트 처리의 타입 캐스팅을 개선할 수 있습니다.

isComposing 속성 접근을 위한 타입 캐스팅이 다소 복잡합니다. KeyboardEventnativeEvent에서 직접 접근하면 더 깔끔합니다.

♻️ 수정 제안
               if (e.key !== 'Enter') return;
-              if (
-                (e.nativeEvent as unknown as { isComposing?: boolean })
-                  ?.isComposing
-              )
-                return;
+              if (e.nativeEvent.isComposing) return;
               e.preventDefault();
               submitEdit();

KeyboardEvent.nativeEvent는 네이티브 KeyboardEvent이며 isComposing 속성을 갖고 있습니다.

components/home/TodoContainer.tsx (3)

4-4: 사용되지 않는 import 제거 필요

useEffect가 import 되었지만 컴포넌트 내에서 사용되지 않습니다.

♻️ 수정 제안
-import { useEffect, useState } from 'react';
+import { useState } from 'react';

10-11: 사용되지 않는 uid prop

uid가 Props 타입에 정의되어 있지만 컴포넌트 내에서 사용되지 않습니다. 이 prop이 필요하지 않다면 제거하거나, 향후 사용 계획이 있다면 주석으로 명시해 주세요.

♻️ 수정 제안
 type Props = {
-  uid: string;
-
   todos: Todo[];
   loading?: boolean;
   error?: string | null;

41-71: 불필요한 Fragment 제거

Card 컴포넌트가 유일한 자식이므로 Fragment(<>...</>)가 필요하지 않습니다.

♻️ 수정 제안
   return (
-    <>
-      <Card
-        title="오늘의 할 일"
-        rightSlot={
-          <button
-            type="button"
-            onClick={() => setShowAddTodo(true)}
-            className="bg-primary hover:bg-primary-hover inline-flex h-8 w-8 items-center justify-center rounded-xl text-white shadow-sm active:scale-95"
-            aria-label="할 일 추가"
-          >
-            <HiPlus size={20} />
-          </button>
-        }
-      >
-        <CheckList
-          items={todos}
-          onToggleTodo={onToggleTodo}
-          emptyText="오늘 할 일이 없습니다"
-          showAdd={showAddTodo}
-          onAdd={async (text) => {
-            await onAddTodo(text);
-            setShowAddTodo(false);
-          }}
-          onRemoveTodo={onRemoveTodo}
-          onEditTodoText={onEditTodoText}
-          onCancelAdd={() => setShowAddTodo(false)}
-        />
-      </Card>
-    </>
+    <Card
+      title="오늘의 할 일"
+      rightSlot={
+        <button
+          type="button"
+          onClick={() => setShowAddTodo(true)}
+          className="bg-primary hover:bg-primary-hover inline-flex h-8 w-8 items-center justify-center rounded-xl text-white shadow-sm active:scale-95"
+          aria-label="할 일 추가"
+        >
+          <HiPlus size={20} />
+        </button>
+      }
+    >
+      <CheckList
+        items={todos}
+        onToggleTodo={onToggleTodo}
+        emptyText="오늘 할 일이 없습니다"
+        showAdd={showAddTodo}
+        onAdd={async (text) => {
+          await onAddTodo(text);
+          setShowAddTodo(false);
+        }}
+        onRemoveTodo={onRemoveTodo}
+        onEditTodoText={onEditTodoText}
+        onCancelAdd={() => setShowAddTodo(false)}
+      />
+    </Card>
   );
components/home/CheckList.tsx (1)

46-50: 중복된 조건 검사

Line 47에서 이미 Enter 키가 아닌 경우 early return 하고 있으므로, Line 49의 e.key === 'Enter' 검사는 중복입니다.

♻️ 수정 제안
 onKeyDown={(e) => {
   if (e.key !== 'Enter') return;
   if (e.nativeEvent.isComposing) return;
-  if (e.key === 'Enter') handleSubmit();
+  handleSubmit();
 }}

Comment on lines +30 to +39
<TodoContainer
uid={uid}
todos={todos}
loading={loading}
error={error}
onToggleTodo={onToggleTodo}
onAddTodo={onAddTodo}
onRemoveTodo={onRemoveTodo}
onEditTodoText={onEditTodoText}
></TodoContainer>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -name "TodoContainer.tsx" -o -name "TodoContainer.ts" -o -name "TodoContainer.jsx" -o -name "TodoContainer.js" 2>/dev/null | head -20

Repository: DeveloperBlog-Devflow/devflow

Length of output: 108


🏁 Script executed:

cat -n ./components/home/TodoContainer.tsx

Repository: DeveloperBlog-Devflow/devflow

Length of output: 2482


TodoContainer에서 uid prop이 전혀 사용되지 않고 있습니다.

TodoContainer 컴포넌트의 Props 타입에는 uid가 정의되어 있지만(10행), 함수 매개변수에서 구조 분해되지 않으며(23-31행) 컴포넌트 본문 어디에서도 사용되지 않습니다. uid prop을 Props 타입에서 제거하거나, 실제로 필요한 경우 사용해야 합니다.

🤖 Prompt for AI Agents
In `@components/home/BottomSection.tsx` around lines 30 - 39, TodoContainer의
Props에 정의된 uid가 함수 매개변수(예: TodoContainer(props) 또는 function TodoContainer({...})
선언)에서 구조분해되지 않고 컴포넌트 본문에서 전혀 사용되지 않으므로 Props 정의와 구현을 일치시켜야 합니다; 해결책은
TodoContainer의 Props 타입에서 uid를 제거 if unused, 또는 컴포넌트 선언(예: TodoContainer의 매개변수
목록)에서 uid를 구조분해하여 실제로 사용하도록 변경(예: 키, 사용자별 필터, API 호출에 전달 등)하고 사용처가 필요한 위치(렌더링 로직
또는 하위 컴포넌트에 전달)로 전달하는 것입니다.

Comment on lines +7 to +16
type CheckListProps = {
items: Todo[];
onToggleTodo: (id: string, currentStatus: boolean) => void;
onToggleTodo: (id: string, checked: boolean) => void;
emptyText?: string;
}
showAdd?: boolean;
onAdd?: (text: string) => void;
onCancelAdd?: () => void;
onRemoveTodo: (id: string) => void;
onEditTodoText: (id: string, text: string) => void;
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

사용되지 않는 props: emptyText, onCancelAdd

emptyTextonCancelAdd가 Props 타입에 정의되어 있지만 컴포넌트에서 사용되지 않습니다:

  • emptyText: 빈 상태 UI가 렌더링되지 않음
  • onCancelAdd: 취소 버튼이 없어 사용자가 추가 모드를 취소할 방법이 없음

TodoContainer에서 onCancelAdd={() => setShowAddTodo(false)}를 전달하고 있으므로, 취소 기능 구현이 필요해 보입니다.

♻️ 수정 제안 (취소 버튼 추가)
 export default function CheckList({
   items,
   onToggleTodo,
   showAdd,
   onAdd,
+  onCancelAdd,
   onRemoveTodo,
   onEditTodoText,
 }: CheckListProps) {

취소 버튼 UI 추가:

           <button
             type="button"
             className="text-primary flex w-10 items-center text-sm font-bold"
             onClick={handleSubmit}
           >
             추가
           </button>
+          <button
+            type="button"
+            className="text-gray-400 flex w-10 items-center text-sm"
+            onClick={onCancelAdd}
+          >
+            취소
+          </button>
🤖 Prompt for AI Agents
In `@components/home/CheckList.tsx` around lines 7 - 16, The CheckListProps lists
emptyText and onCancelAdd but the CheckList component never uses them; update
the CheckList component to render emptyText when items.length === 0 (falling
back to a sensible default string) and add a cancel control in the add-todo UI
that calls the onCancelAdd prop; specifically locate the CheckList component
(props named emptyText and onCancelAdd) and: 1) render the empty state using
emptyText when there are no items, and 2) add a cancel button next to the
add/save controls that invokes onCancelAdd() (ensuring showAdd toggling remains
driven by the parent via onCancelAdd).

Comment on lines +28 to +34
const handleSubmit = () => {
if (showAdd) setDraft('');
const text = draft.trim();
if (!text) return;
onAdd?.(text);
setDraft('');
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

버그: 할 일 추가가 동작하지 않음

handleSubmit 함수의 로직 순서에 문제가 있습니다. Line 29에서 showAdd가 true일 때 draft를 먼저 비우고, Line 30에서 빈 문자열을 trim하므로 text는 항상 빈 문자열이 됩니다. 결과적으로 Line 31에서 항상 early return되어 onAdd가 절대 호출되지 않습니다.

🐛 수정 제안
 const handleSubmit = () => {
-  if (showAdd) setDraft('');
   const text = draft.trim();
-  if (!text) return;
+  if (!text) {
+    setDraft('');
+    return;
+  }
   onAdd?.(text);
   setDraft('');
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleSubmit = () => {
if (showAdd) setDraft('');
const text = draft.trim();
if (!text) return;
onAdd?.(text);
setDraft('');
};
const handleSubmit = () => {
const text = draft.trim();
if (!text) {
setDraft('');
return;
}
onAdd?.(text);
setDraft('');
};
🤖 Prompt for AI Agents
In `@components/home/CheckList.tsx` around lines 28 - 34, The bug is that
handleSubmit clears draft before reading it, so text is always empty; update
handleSubmit (function handleSubmit, variables draft, showAdd, onAdd, setDraft)
to read and trim draft first (const text = draft.trim()), return early if empty,
then call onAdd?.(text) and only afterwards clear the draft (setDraft(''));
remove or relocate the initial if (showAdd) setDraft('') so clearing doesn't
happen before computing text.

@DongEun02 DongEun02 closed this Jan 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants