8000 Feature: 메모 선택 by guesung · Pull Request #113 · guesung/Web-Memo · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Feature: 메모 선택 #113

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Dec 3, 2024
Merged

Feature: 메모 선택 #113

merged 18 commits into from
Dec 3, 2024

Conversation

guesung
Copy link
Owner
@guesung guesung commented Dec 3, 2024

Summary by CodeRabbit

  • 새로운 기능

    • 메모의 삽입 및 업데이트를 지원하는 useMemosUpsertMutation 훅 추가.
    • 여러 메모를 삭제할 수 있는 useDeleteMemosMutation 훅 추가.
    • 선택된 메모의 수를 표시하는 MemoOptionHeader 컴포넌트 추가.
    • 메모 선택 기능을 향상시키는 MemoGridMemoItem 컴포넌트 수정.
    • 키보드 바인딩을 위한 useKeyboardBind 훅 추가.
    • 다국어 지원을 위한 선택된 메모 수 표시 메시지 추가 (영어 및 한국어 번역).
  • 버그 수정

    • 메모 삭제 후 복구 기능 개선.
  • 문서화

    • 새로운 유틸리티 함수 isAllSame 추가.

Summary by CodeRabbit

  • 새로운 기능

    • 여러 메모를 삽입하거나 업데이트할 수 있는 useMemosUpsertMutation 훅 추가.
    • 여러 메모를 삭제할 수 있는 useDeleteMemosMutation 훅 추가.
    • 선택된 메모의 수를 표시하는 기능 추가 (영어 및 한국어 번역 포함).
    • 애니메이션 효과를 위한 MOTION_VARIANTS 상수 추가.
    • 메모 선택 기능 향상 및 선택된 메모를 관리하는 MemoOptionHeader 컴포넌트 추가.
  • 버그 수정

    • 메모 처리 로직 개선 및 오류 처리 강화.
  • 문서화

    • 새로운 컴포넌트 및 훅에 대한 설명 추가.
  • 리팩토링

    • 메모 관련 컴포넌트 및 훅의 구조 개선.

Copy link
coderabbitai bot commented Dec 3, 2024

Walkthrough

이 풀 리퀘스트는 메모의 삽입 및 업데이트를 위한 새로운 훅 useMemosUpsertMutation을 추가하고, 여러 메모를 삭제할 수 있는 deleteMemosupsertMemos 함수를 포함하는 여러 파일의 변경 사항을 포함합니다. 또한 MemoOptionMemoGrid와 같은 컴포넌트에서 여러 메모를 처리할 수 있도록 인터페이스와 기능이 수정되었습니다. 이로 인해 메모 관리 방식이 개선되었습니다.

Changes

파일 경로 변경 요약
packages/shared/src/hooks/supabase/index.ts useMemosUpsertMutation 함수의 새로운 export 추가.
packages/shared/src/hooks/supabase/useMemosUpsertMutation.ts useMemosUpsertMutation 훅 추가 및 관련 타입과 인터페이스 정의.
packages/shared/src/utils/Supabase.ts deleteMemosupsertMemos 함수 추가, 기존 upsertMemo 함수 이름 변경.
packages/shared/src/utils/index.ts isAllSame 모듈의 새로운 export 추가.
packages/shared/src/utils/isAllSame.ts 배열의 모든 요소가 동일한지 확인하는 isAllSame 유틸리티 함수 추가.
packages/web/src/app/[lng]/memos/components/MemoCardFooter/MemoOption.tsx MemoOptionProps 인터페이스 수정, 여러 메모를 처리하도록 변경.
packages/web/src/app/[lng]/memos/components/MemoCardFooter/index.tsx MemoOption 컴포넌트에서 memoIdmemos로 변경.
packages/web/src/app/[lng]/memos/components/MemoCardHeader/index.tsx MemoCardHeaderProps 인터페이스에 새로운 props 추가 및 버튼 추가.
packages/web/src/app/[lng]/memos/components/MemoView/MemoGrid.tsx selectedMemos 상태 추가, 메모 선택 기능 개선.
packages/web/src/app/[lng]/memos/components/MemoView/MemoItem.tsx MemoItemProps 인터페이스 수정, 선택 상태에 따른 동작 추가.
packages/web/src/app/[lng]/memos/components/MemoView/MemoOptionHeader.tsx 새로운 MemoOptionHeader 컴포넌트 추가, 선택된 메모 수 표시.
packages/web/src/hooks/index.ts useDeleteMemosMutation 훅 추가 및 useMemoDeleteMutation 훅 제거.
packages/web/src/hooks/useDeleteMemosMutation.ts useMemoDeleteMutation 훅 이름 변경 및 여러 메모 삭제 기능으로 업데이트.

Possibly related PRs

  • Feature: 세팅 페이지 구현 #100: useMemosUpsertMutation 훅 추가는 useMemoPostMutation 훅의 변경 사항과 관련이 있으며, 둘 다 Supabase에서 메모 관리와 관련된 작업을 포함합니다.
  • Feature/ux #101: useMemoQuery 함수의 변경 사항은 메모 검색과 관련이 있으며, 이는 주 PR에서 도입된 upsert 기능과 밀접하게 연결됩니다.
  • Feature/ux 3 #109: useMemosQuery 함수의 수정은 새로운 useMemosUpsertMutation 기능과 관련이 있습니다.
  • Hotfix: 라우팅 등 자잘한 이슈 해결 #111: useMemoQuery 함수의 변경 사항은 메모 관리의 전반적인 맥락과 관련이 있습니다.
  • Feature/calendar #112: MemoCalendar 컴포넌트의 도입은 주 PR에서 새롭게 추가된 메모 처리 기능과 관련이 있습니다.

🐰 새로운 훅이 생겼어요,
메모를 쉽게 다룰 수 있어요.
여러 개를 한 번에 처리하고,
오류도 잘 잡아주니,
메모리의 기쁨을 함께 나눠요! ✨


📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c552d6e and 113861b.

📒 Files selected for processing (2)
  • packages/web/src/app/[lng]/memos/components/MemoCardFooter/index.tsx (3 hunks)
  • packages/web/src/app/[lng]/memos/components/MemoDialog/index.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/web/src/app/[lng]/memos/components/MemoCardFooter/index.tsx
🔇 Additional comments (1)
packages/web/src/app/[lng]/memos/components/MemoDialog/index.tsx (1)

98-98: prop 이름 변경이 적절해 보입니다.

isHovered에서 isOptionShown으로의 prop 이름 변경은 해당 prop의 목적을 더 명확하게 표현합니다. 이는 코드의 가독성과 유지보수성을 향상시킵니다.

다음 스크립트를 실행하여 prop 이름 변경이 전체 코드베이스에서 일관되게 적용되었는지 확인하세요:

✅ Verification successful

prop 이름이 일관되게 변경되었으며 올바르게 사용되고 있습니다.

isHovered에서 isOptionShown으로의 prop 이름 변경이 코드베이스 전체에서 일관되게 적용되었음을 확인했습니다. MemoCardFooter 컴포넌트의 모든 사용처에서 새로운 prop 이름을 사용하고 있으며, 특히 MemoItem 컴포넌트에서는 isHovered 상태와 isSelecting 상태를 조합하여 적절하게 활용하고 있습니다.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the prop name change across the codebase

# Check for any remaining usages of isHovered prop
rg "isHovered.*MemoCardFooter"

# Check for consistent usage of new isOptionShown prop
rg "isOptionShown.*MemoCardFooter"

Length of output: 250


Script:

#!/bin/bash
# Let's check the interface/type definition and usages
ast-grep --pattern 'interface MemoCardFooterProps {
  $$$
}'

# Also check for any other occurrences of MemoCardFooter component usage
rg "<MemoCardFooter" -A 2

Length of output: 817


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

Copy link
@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

🧹 Outside diff range and nitpick comments (9)
packages/shared/src/hooks/supabase/useMemosUpsertMutation.ts (1)

18-18: 불필요한 await 사용

mutationFn에서 await를 사용하지 않아도 됩니다. 함수 자체가 비동기 함수이므로 await 없이 바로 반환할 수 있습니다.

수정 제안:

- mutationFn: async memoRequest => await upsertMemos(supabaseClient, memoRequest),
+ mutationFn: memoRequest => upsertMemos(supabaseClient, memoRequest),
packages/web/src/app/[lng]/memos/components/MemoCardFooter/MemoOption.tsx (2)

38-38: 'queryClient' 변수가 사용되지 않습니다

queryClient 변수가 선언되었지만 사용되지 않습니다. 사용되지 않는 코드는 제거하는 것이 좋습니다.

수정 제안:

- const queryClient = useQueryClient();
🧰 Tools
🪛 eslint

[error] 38-38: 'queryClient' is assigned a value but never used.

(@typescript-eslint/no-unused-vars)


54-54: 디버깅 코드 제거 필요

console.log는 디버깅 용도로 사용되며, 배포 전에는 제거하는 것이 좋습니다.

수정 제안:

- console.log('undo', memos);
packages/web/src/app/[lng]/memos/components/MemoView/MemoOptionHeader.tsx (1)

13-20: 접근성 개선이 필요합니다

헤더 요소의 접근성을 개선하기 위한 몇 가지 제안사항입니다:

-<header className="fixed inset-x-0 top-0 z-50 flex h-12 w-full items-center justify-between bg-white shadow-sm">
+<header 
+  className="fixed inset-x-0 top-0 z-50 flex h-12 w-full items-center justify-between bg-white shadow-sm"
+  role="banner"
+  aria-label={t('memos.selectionHeader')}
+>
   <div className="flex items-center gap-2 px-4">
-    <span className="text-md font-semibold">{selectedMemos.length}개 선택</span>
+    <span className="text-md font-semibold" role="status" aria-live="polite">
+      {t('memos.selected', { count: selectedMemos.length })}
+    </span>
   </div>
packages/shared/src/utils/Supabase.ts (1)

25-26: 배치 작업의 트랜잭션 처리가 필요합니다

여러 메모를 동시에 업서트하는 작업에서 일부만 성공하고 일부는 실패할 수 있는 상황을 고려해야 합니다.

트랜잭션 처리를 위해 다음과 같은 패턴을 고려해보세요:

export const upsertMemos = async (supabaseClient: MemoSupabaseClient, memoRequest: MemoTable['Insert'][]) => {
  const { data, error } = await supabaseClient
    .rpc('batch_upsert_memos', { 
      memos: memoRequest 
    });
  
  if (error) throw error;
  return data;
}

이를 위해서는 Supabase에 저장 프로시저를 추가해야 합니다. 필요하다면 저장 프로시저 생성을 도와드릴 수 있습니다.

packages/web/src/app/[lng]/memos/components/MemoCardHeader/index.tsx (1)

21-31: 선택 버튼의 접근성 개선이 필요합니다.

선택 버튼이 적절하게 구현되었지만, 다음 사항들을 개선하면 좋을 것 같습니다:

  1. 스크린 리더 사용자를 위한 aria-label 추가
  2. 선택 상태를 나타내는 aria-pressed 속성 추가
 <Button
   id={String(memo.id)}
   variant="ghost"
   size="icon"
+  aria-label={`메모 선택하기: ${memo.title}`}
+  aria-pressed={isSelected}
   className={cn('absolute -left-4 -top-4 z-10 rounded-full bg-white', {
     'opacity-100': isHovered || isSelected,
     'opacity-0': !isHovered && !isSelected,
   })}
   >
packages/web/src/app/[lng]/memos/components/MemoView/MemoItem.tsx (2)

31-34: 선택 로직이 명확하게 구현되었습니다.

선택 모드일 때의 동작이 적절하게 처리되어 있습니다. 다만, 키보드 접근성을 위해 Space 키 이벤트도 처리하면 좋을 것 같습니다.

 if (isSelecting) {
   onSelect(event);
+  if (event instanceof KeyboardEvent && event.key === ' ') {
+    event.preventDefault();
+  }
   return;
 }

59-65: 성능 최적화를 위한 제안

onMouseEnteronMouseLeave 핸들러를 메모이제이션하면 불필요한 리렌더링을 방지할 수 있습니다.

+const handleMouseEnter = useCallback(() => {
+  setIsHovered(true);
+}, []);
+
+const handleMouseLeave = useCallback(() => {
+  setIsHovered(false);
+}, []);
packages/web/src/app/[lng]/memos/components/MemoView/MemoGrid.tsx (1)

56-65: 무한 스크롤 로직의 성능 최적화가 필요합니다.

현재 구현은 기능적으로는 문제가 없지만, 다음과 같은 최적화를 고려해보시면 좋을 것 같습니다:

  1. 불필요한 배열 복사 최소화
  2. 계산 로직 메모이제이션
+const calculateNextItems = useCallback((currentItems: any[], nextGroupKey: number, maxAddItem: number) => {
+  return getMemoItems(nextGroupKey, maxAddItem);
+}, []);

  => {
   if (items.length >= memos.length) return;

   const nextGroupKey = (+e.groupKey! || 0) + 1;
   const maxAddItem = items.length + MEMO_UNIT > memos.length ? memos.length - items.length : MEMO_UNIT;

   if (maxAddItem === 0) return;

-  setItems([...items, ...getMemoItems(nextGroupKey, maxAddItem)]);
+  setItems(currentItems => [...currentItems, ...calculateNextItems(currentItems, nextGroupKey, maxAddItem)]);
 }}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 3126f4d and 4e99534.

📒 Files selected for processing (13)
  • packages/shared/src/hooks/supabase/index.ts (1 hunks)
  • packages/shared/src/hooks/supabase/useMemosUpsertMutation.ts (1 hunks)
  • packages/shared/src/utils/Supabase.ts (1 hunks)
  • packages/shared/src/utils/index.ts (1 hunks)
  • packages/shared/src/utils/isAllSame.ts (1 hunks)
  • packages/web/src/app/[lng]/memos/components/MemoCardFooter/MemoOption.tsx (5 hunks)
  • packages/web/src/app/[lng]/memos/components/MemoCardFooter/index.tsx (1 hunks)
  • packages/web/src/app/[lng]/memos/components/MemoCardHeader/index.tsx (1 hunks)
  • packages/web/src/app/[lng]/memos/components/MemoView/MemoGrid.tsx (2 hunks)
  • packages/web/src/app/[lng]/memos/components/MemoView/MemoItem.tsx (4 hunks)
  • packages/web/src/app/[lng]/memos/components/MemoView/MemoOptionHeader.tsx (1 hunks)
  • packages/web/src/hooks/index.ts (1 hunks)
  • packages/web/src/hooks/useDeleteMemosMutation.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/shared/src/utils/isAllSame.ts
🧰 Additional context used
🪛 eslint
packages/web/src/app/[lng]/memos/components/MemoCardFooter/MemoOption.tsx

[error] 38-38: 'queryClient' is assigned a value but never used.

(@typescript-eslint/no-unused-vars)

🔇 Additional comments (7)
packages/web/src/hooks/index.ts (1)

1-1: 변경사항 확인됨

useDeleteMemosMutation 훅이 올바르게 추가되었습니다.

packages/shared/src/utils/index.ts (1)

6-6: 변경사항 확인됨

isAllSame 유틸리티가 올바르게 내보내졌습니다.

packages/shared/src/hooks/supabase/index.ts (1)

7-7: 변경사항 확인됨

useMemosUpsertMutation 훅이 올바르게 내보내졌습니다.

packages/web/src/hooks/useDeleteMemosMutation.ts (1)

12-16: 타입 안전성이 잘 구현되어 있습니다!

mutation 함수의 타입 정의가 명확하며, 응답 타입(MemoSupabaseResponse)과 에러 처리가 잘 구현되어 있습니다. 쿼리 무효화도 적절히 처리되어 있습니다.

packages/web/src/app/[lng]/memos/components/MemoCardHeader/index.tsx (1)

13-15: Props 인터페이스가 적절하게 확장되었습니다.

선택 기능을 위한 props가 명확하게 정의되어 있으며, 이벤트 타입이 올바르게 지정되었습니다.

packages/web/src/app/[lng]/memos/components/MemoView/MemoItem.tsx (1)

14-16: 선택 관련 Props가 적절하게 정의되었습니다.

선택 기능을 위한 props가 명확하게 정의되어 있으며, 타입 안전성이 보장됩니다.

packages/web/src/app/[lng]/memos/components/MemoCardFooter/index.tsx (1)

103-103: 단일 메모를 배열로 전달하는 방식이 적절합니다.

MemoOption 컴포넌트의 props 변경이 잘 이루어졌습니다. 단일 메모를 배열로 래핑하여 전달함으로써 다중 메모 처리 기능을 지원하면서도 기존 기능을 유지하고 있습니다.

다음 스크립트를 실행하여 MemoOption 컴포넌트가 새로운 props 타입을 올바르게 처리하는지 확인하겠습니다:

✅ Verification successful

memos prop 타입 변경이 올바르게 구현되었습니다.

MemoOption 컴포넌트가 memos 배열을 완벽하게 처리하고 있습니다:

  • 타입 정의가 명확하게 MemoRow[]로 되어있습니다
  • 컴포넌트 내부에서 배열 처리 로직이 적절하게 구현되어 있습니다
    • 카테고리 변경 시 map 함수로 모든 메모 처리
    • 삭제 기능에서도 배열 처리 지원
  • 다른 컴포넌트들(MemoOptionHeader, MemoCardFooter)에서도 일관되게 사용되고 있습니다
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: MemoOption 컴포넌트의 props 타입과 구현을 확인합니다.

# MemoOption 컴포넌트의 타입 정의와 구현을 검색
ast-grep --pattern 'interface $PROPS {
  $$$
  memos: $TYPE
  $$$
}'

# memos prop의 사용 패턴을 검색
rg -A 3 'memos\['

Length of output: 90


Script:

#!/bin/bash
# Let's try different approaches to find MemoOption component and its usage

# Find MemoOption component definition
ast-grep --pattern 'const MemoOption = $$$'

# Find files containing MemoOption
fd MemoOption

# Search for type definitions related to MemoOption
rg "type.*MemoOption" -A 3
rg "interface.*MemoOption" -A 3

# Search for memos prop usage in a different way
rg "memos=" -A 2

Length of output: 2124


Script:

#!/bin/bash
# Let's examine t
10000
he MemoOption component implementation to verify the array handling

# Get the content of MemoOption component
cat packages/web/src/app/[lng]/memos/components/MemoCardFooter/MemoOption.tsx

# Find all usages of MemoOption component
rg "<MemoOption" -A 2 -B 2

Length of output: 6491

Copy link
@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: 1

🧹 Outside diff range and nitpick comments (3)
packages/web/src/hooks/useDeleteMemosMutation.ts (1)

19-24: 에러 메시지의 구체화로 디버깅 용이성 향상 제안

현재 NoMemosError를 발생시키고 있는데, 에러 메시지를 구체적으로 작성하면 디버깅에 도움이 될 수 있습니다. 예를 들어, 어떤 상황에서 메모가 없는지 명시해주는 것이 좋습니다.

- if (!previousMemos) throw new NoMemosError();
+ if (!previousMemos) throw new Error('이전에 불러온 메모가 없습니다.');

...

- if (!previousMemosData) throw new NoMemosError();
+ if (!previousMemosData) throw new Error('이전에 불러온 메모 데이터가 없습니다.');
packages/web/src/app/[lng]/memos/components/MemoCardFooter/MemoOption.tsx (2)

Line range hint 76-105: 카테고리 변경 시 유효성 검사가 필요합니다.

카테고리 변경 작업에서 다음과 같은 유효성 검사가 추가되면 좋을 것 같습니다:

  1. 카테고리 ID의 유효성 검사
  2. 빈 메모 배열 처리
  3. 중복 요청 방지
 const handleCategoryChange = (categoryId: string) => {
+  if (!memos.length) return;
+  if (!categories?.some(category => category.id === Number(categoryId))) {
+    toast({
+      title: t('toastTitle.error'),
+      description: t('toastDescription.invalidCategory'),
+      variant: 'destructive',
+    });
+    return;
+  }
   mutateUpsertMemo(
     memos.map(memo => ({ ...memo, category_id: Number(categoryId) })),
     // ... rest of the implementation
   );
 };

106-106: 접근성 개선이 필요합니다.

백스페이스 키 바인딩에 대한 사용자 안내가 필요합니다. 다음 사항들을 고려해주세요:

  1. 키보드 단축키에 대한 툴팁 추가
  2. 스크린 리더 사용자를 위한 ARIA 레이블 추가
+import { Tooltip, TooltipContent, TooltipTrigger } from '@src/components/ui/tooltip';

 // ... in the JSX
-<Button variant="ghost" size="icon"  => e.stopPropagation()}>
+<Tooltip>
+  <TooltipTrigger asChild>
+    <Button
+      variant="ghost"
+      size="icon"
+       => e.stopPropagation()}
+      aria-label={t('aria.deleteWithBackspace')}
+    >
+      <EllipsisVerticalIcon size={16} />
+    </Button>
+  </TooltipTrigger>
+  <TooltipContent>
+    <p>{t('tooltip.pressBackspaceToDelete')}</p>
+  </TooltipContent>
+</Tooltip>
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4e99534 and 29367ef.

📒 Files selected for processing (9)
  • packages/shared/src/hooks/index.ts (1 hunks)
  • packages/shared/src/hooks/supabase/useMemosUpsertMutation.ts (1 hunks)
  • packages/shared/src/hooks/useCloseOnEscape.ts (0 hunks)
  • packages/shared/src/hooks/useKeyboardBind.ts (1 hunks)
  • packages/web/src/app/[lng]/memos/components/MemoCardFooter/MemoOption.tsx (4 hunks)
  • packages/web/src/app/[lng]/memos/components/MemoView/MemoGrid.tsx (2 hunks)
  • packages/web/src/app/[lng]/memos/components/MemoView/MemoOptionHeader.tsx (1 hunks)
  • packages/web/src/components/ui/dialog.tsx (2 hunks)
  • packages/web/src/hooks/useDeleteMemosMutation.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • packages/shared/src/hooks/useCloseOnEscape.ts
🔇 Additional comments (9)
packages/web/src/hooks/useDeleteMemosMutation.ts (1)

13-31: 훅의 구현이 적절하며, 삭제 로직이 올바르게 적용되었습니다.

useDeleteMemosMutation 훅이 React Query의 useMutation을 활용하여 메모 삭제 기능을 효율적으로 구현하고 있습니다. 낙관적 업데이트를 통해 사용자 경험을 향상시키는 부분도 잘 처리되었습니다.

packages/shared/src/hooks/index.ts (1)

5-5: useKeyboardBind 훅의 추가를 확인했습니다.

새로운 훅 useKeyboardBind가 추가되어 키보드 이벤트 처리가 더욱 효율적으로 개선되었습니다.

packages/shared/src/hooks/useKeyboardBind.ts (1)

10-18: 키보드 이벤트 리스너의 클린업 처리가 적절합니다.

useEffect 훅을 사용하여 키보드 이벤트 리스너의 등록과 해제가 올바르게 구현되었습니다. 의존성 배열에 keycallback을 포함하여 변경 시마다 효과가 재실행되도록 한 부분도 좋습니다.

packages/web/src/app/[lng]/memos/components/MemoView/MemoOptionHeader.tsx (1)

21-21: 🛠️ Refactor suggestion

국제화(i18n) 처리가 필요합니다

{selectedMemos.length}개 선택됨 텍스트가 하드코딩되어 있습니다. 다국어 지원을 위해 번역 키를 사용하는 것이 좋습니다.

수정 제안:

- <span className="text-md font-semibold">{selectedMemos.length}개 선택됨</span>
+ <span className="text-md font-semibold">
+   {t('memos.selected', { count: selectedMemos.length })}
+ </span>
packages/shared/src/hooks/supabase/useMemosUpsertMutation.ts (1)

29-35: ⚠️ Potential issue

불변성 유지와 새로운 메모 추가를 위한 로직 개선 필요

현재 previousMemosData를 직접 수정하고 있습니다. React Query의 캐시 데이터를 직접 변경하면 예기치 않은 부작용이 발생할 수 있으므로, 복사본을 만들어 수정하는 것이 좋습니다. 또한, 새로운 메모를 추가할 때를 고려하여 로직을 개선해야 합니다.

수정 제안:

+ const updatedMemosData = [...previousMemosData];
memoRequest.forEach(memo => {
  const currentMemoIndex = updatedMemosData.findIndex(previousMemo => previousMemo.id === memo.id);
  const currentMemoBase = updatedMemosData.find(previousMemo => previousMemo.id === memo.id);

- if (currentMemoIndex === -1 || !currentMemoBase) previousMemosData.unshift(memo as MemoRow);
- else previousMemosData.splice(currentMemoIndex, 1, { ...currentMemoBase, ...memo });
+ if (currentMemoIndex === -1 || !currentMemoBase) {
+   // 새로운 메모인 경우, updatedMemosData에 추가
+   updatedMemosData.unshift(memo as MemoRow);
+ } else {
+   updatedMemosData.splice(currentMemoIndex, 1, { ...currentMemoBase, ...memo });
+ }
});

- await queryClient.setQueryData(QUERY_KEY.memos(), { ...previousMemos, data: previousMemosData });
+ await queryClient.setQueryData(QUERY_KEY.memos(), { ...previousMemos, data: updatedMemosData });
packages/web/src/app/[lng]/memos/components/MemoView/MemoGrid.tsx (3)

42-46: 구현이 깔끔합니다!

선택 해제 로직과 키보드 바인딩이 잘 구현되어 있습니다.


31-40: 🛠️ Refactor suggestion

메모 선택 로직 개선이 필요합니다.

이전 리뷰에서 제기된 문제가 아직 해결되지 않았습니다. 선택 로직에서 다음과 같은 개선이 필요합니다:

  1. 타입 안전성을 위해 ! 연산자 사용을 피하고 옵셔널 체이닝을 사용
  2. 메모 검색 성능 최적화를 위한 캐싱 구현
-else setSelectedMemos(prev => [...prev, memos.find(memo => memo.id === id)!]);
+else {
+  const selectedMemo = memos.find(memo => memo.id === id);
+  if (selectedMemo) {
+    setSelectedMemos(prev => [...prev, selectedMemo]);
+  }
+}

68-78: 🛠️ Refactor suggestion

메모 렌더링 최적화가 필요합니다.

이전 리뷰에서 제기된 성능 문제가 아직 해결되지 않았습니다:

  1. 불필요한 memo.at() 호출 최소화
  2. 선택 상태 확인 로직 최적화
+const memoItem = memos.at(item.key);
+const isItemSelected = memoItem ? selectedMemos.some(memo => memo.id === memoItem.id) : false;

<MemoItem
  key={item.key + gridKey}
-  memo={memos.at(item.key)}
+  memo={memoItem}
  lng={lng}
-  isSelected={selectedMemos.some(memo => memo.id === memos.at(item.key)?.id)}
+  isSelected={isItemSelected}
  
  isSelecting={selectedMemos.length > 0}
  data-grid-groupkey={item.groupKey}
/>
packages/web/src/components/ui/dialog.tsx (1)

38-38: 키보드 이벤트 처리가 개선되었습니다!

useKeyboardBind 훅을 사용한 구현이 깔끔하고 재사용성이 높습니다.

Copy link
@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: 1

🧹 Outside diff range and nitpick comments (6)
packages/web/src/app/[lng]/memos/components/MemoCardHeader/index.tsx (1)

21-31: 선택 버튼 UI 구현이 개선될 수 있습니다

현재 구현은 기능적으로는 문제가 없으나, 다음과 같은 개선사항을 고려해보세요:

  1. 버튼의 위치가 절대 위치로 지정되어 있어 컨테이너 크기 변경 시 문제가 될 수 있습니다.
  2. 접근성을 위해 aria-label을 추가하면 좋을 것 같습니다.
 <Button
   id={String(memo.id)}
   variant="ghost"
   size="icon"
+  aria-label={`메모 ${memo.title} 선택하기`}
   className={cn('absolute -left-4 -top-4 z-10 rounded-full bg-white', {
     'opacity-100': isHovered || isSelected,
     'opacity-0': !isHovered && !isSelected,
   })}
   >
packages/shared/src/hooks/supabase/useMemosUpsertMutation.ts (2)

31-37: 메모 업데이트 로직이 개선되었습니다

이전 리뷰 의견이 잘 반영되어 다음과 같은 개선이 이루어졌습니다:

  1. 새로운 메모 추가 시 unshift를 사용하여 목록 최상단에 추가
  2. 기존 메모 업데이트 시 불변성을 유지하면서 처리

다만, 타입 안전성을 위해 다음과 같은 개선을 제안합니다:

- if (currentMemoIndex === -1 || !currentMemoBase) updatedMemosData.unshift(memo as MemoRow);
+ if (currentMemoIndex === -1 || !currentMemoBase) {
8000

+   const newMemo: MemoRow = {
+     ...memo,
+     created_at: new Date().toISOString(),
+     updated_at: new Date().toISOString()
+   };
+   updatedMemosData.unshift(newMemo);
+ }

19-27: 에러 처리가 중복되어 있습니다

previousMemospreviousMemosData에 대한 null 체크가 분리되어 있어 중복된 코드가 발생합니다. 다음과 같이 개선할 수 있습니다:

- if (!previousMemos) throw new NoMemosError();
- 
- const { data: previousMemosData } = previousMemos;
- 
- if (!previousMemosData) throw new NoMemosError();

+ const previousMemosData = previousMemos?.data;
+ if (!previousMemosData) throw new NoMemosError();
packages/web/src/app/[lng]/memos/components/MemoView/MemoItem.tsx (1)

29-32: 선택 로직에 대한 주석 추가가 필요합니다.

선택 모드일 때의 동작 흐름을 설명하는 주석을 추가하면 코드의 가독성이 향상될 것 같습니다.

+  // 선택 모드일 경우 메모 상세 페이지로의 이동을 방지하고 선택 상태만 토글합니다
   if (isSelecting) {
     onSelect(event);
     return;
   }
packages/web/src/app/[lng]/memos/components/MemoView/MemoGrid.tsx (1)

45-49: 키보드 바인딩에 에러 처리 추가가 필요합니다.

선택 해제 시 발생할 수 있는 예외 상황에 대한 처리를 추가하면 좋을 것 같습니다.

 const closeMemoOption = () => {
+  try {
     setSelectedMemos([]);
+  } catch (error) {
+    console.error('메모 선택 해제 중 오류가 발생했습니다:', error);
+  }
 };

 useKeyboardBind({ key: 'Escape', callback: closeMemoOption });
packages/web/src/app/[lng]/memos/components/MemoCardFooter/MemoOption.tsx (1)

Line range hint 76-104: 카테고리 변경 시 로딩 상태 처리가 필요합니다.

카테고리 변경 작업 중에 사용자에게 진행 상태를 표시하지 않고 있습니다. 이는 사용자 경험을 저하시킬 수 있습니다.

다음과 같이 로딩 상태를 추가하는 것을 제안합니다:

+ const [isUpdating, setIsUpdating] = useState(false);

  const handleCategoryChange = (categoryId: string) => {
+   setIsUpdating(true);
    mutateUpsertMemo(
      memos.map(memo => ({ ...memo, category_id: Number(categoryId) })),
      {
        onSuccess: () => {
          // ... existing success handling
+         setIsUpdating(false);
        },
+       onError: () => {
+         setIsUpdating(false);
+         toast({
+           title: t('toastTitle.error'),
+           description: t('toastDescription.categoryChangeFailed'),
+           variant: 'destructive',
+         });
+       },
      },
    );
  };

그리고 Select 컴포넌트에 로딩 상태를 표시:

- <Select  defaultValue={defaultCategoryId}>
+ <Select 
+    
+   defaultValue={defaultCategoryId}
+   disabled={isUpdating}
+ >
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 29367ef and 6fcc79c.

📒 Files selected for processing (10)
  • packages/shared/src/hooks/supabase/useMemoPatchMutation.ts (1 hunks)
  • packages/shared/src/hooks/supabase/useMemosUpsertMutation.ts (1 hunks)
  • packages/web/src/app/[lng]/memos/components/MemoCardFooter/MemoOption.tsx (4 hunks)
  • packages/web/src/app/[lng]/memos/components/MemoCardHeader/index.tsx (1 hunks)
  • packages/web/src/app/[lng]/memos/components/MemoView/MemoGrid.tsx (2 hunks)
  • packages/web/src/app/[lng]/memos/components/MemoView/MemoItem.tsx (4 hunks)
  • packages/web/src/app/[lng]/memos/components/MemoView/MemoOptionHeader.tsx (1 hunks)
  • packages/web/src/app/[lng]/memos/components/legacy/MemoTable.tsx (0 hunks)
  • packages/web/src/modules/i18n/locales/en/translation.json (1 hunks)
  • packages/web/src/modules/i18n/locales/ko/translation.json (1 hunks)
💤 Files with no reviewable changes (1)
  • packages/web/src/app/[lng]/memos/components/legacy/MemoTable.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/web/src/app/[lng]/memos/components/MemoView/MemoOptionHeader.tsx
🔇 Additional comments (10)
packages/shared/src/hooks/supabase/useMemoPatchMutation.ts (1)

32-41: 캐시 데이터 처리 개선이 잘 이루어졌습니다!

불변성을 유지하면서 캐시 데이터를 처리하도록 개선되었습니다. updatedMemosData를 사용하여 원본 데이터를 직접 수정하지 않고 안전하게 업데이트하는 방식으로 변경된 것이 좋습니다.

packages/web/src/app/[lng]/memos/components/MemoCardHeader/index.tsx (1)

11-15: 인터페이스가 명확하게 정의되었습니다

선택 기능을 위한 props가 잘 정의되어 있습니다. 특히 onSelect 핸들러가 마우스와 키보드 이벤트를 모두 지원하여 접근성이 고려된 점이 좋습니다.

packages/web/src/app/[lng]/memos/components/MemoView/MemoItem.tsx (2)

13-16: Props 인터페이스가 잘 정의되어 있습니다!

필수 props들이 명확하게 타입이 지정되어 있으며, 이벤트 핸들러의 타입도 정확하게 정의되어 있습니다.


58-64: 스타일링이 깔끔하게 구현되어 있습니다!

cn 유틸리티를 사용한 조건부 스타일링과 트랜지션 효과가 잘 적용되어 있습니다.

packages/web/src/modules/i18n/locales/ko/translation.json (1)

99-100: 번역이 자연스럽게 추가되었습니다!

선택된 메모 개수를 표시하는 번역문이 기존 번역 스타일과 일관성 있게 작성되었습니다.

packages/web/src/app/[lng]/memos/components/MemoView/MemoGrid.tsx (2)

41-42: 선택 상태 관리 로직 개선이 필요합니다.

타입 안전성과 성능 최적화를 위해 기존 리뷰 의견을 반영한 수정이 필요합니다.

-if (isMemoSelected(id)) setSelectedMemos(prev => prev.filter(memo => memo.id !== id));
-else setSelectedMemos(prev => [...prev, memos.find(memo => memo.id === id)!]);
+if (isMemoSelected(id)) {
+  setSelectedMemos(prev => prev.filter(memo => memo.id !== id));
+} else {
+  const selectedMemo = memos.find(memo => memo.id === id);
+  if (selectedMemo) {
+    setSelectedMemos(prev => [...prev, selectedMemo]);
+  }
+}

72-81: 메모 렌더링 최적화가 필요합니다.

이전 리뷰 의견을 반영하여 메모 아이템의 렌더링 성능을 개선할 수 있습니다.

+const memoItem = memos.at(item.key);
+const isItemSelected = memoItem ? selectedMemos.some(memo => memo.id === memoItem.id) : false;

 <MemoItem
   key={item.key + gridKey}
-  memo={memos.at(item.key)!}
+  memo={memoItem}
   lng={lng}
-  isSelected={isMemoSelected(memos.at(item.key)!.id)}
+  isSelected={isItemSelected}
   
   isSelecting={isAnyMemoSelected}
   data-grid-groupkey={item.groupKey}
 />
packages/web/src/modules/i18n/locales/en/translation.json (1)

99-100: 번역 키가 적절하게 추가되었습니다.

메모 선택 기능을 위한 새로운 번역 키가 올바르게 추가되었으며, count 파라미터를 사용하는 포맷도 적절합니다.

packages/web/src/app/[lng]/memos/components/MemoCardFooter/MemoOption.tsx (2)

2-2: 컴포넌트 인터페이스가 적절하게 변경되었습니다.

단일 메모에서 다중 메모 처리로의 전환이 깔끔하게 이루어졌습니다. 필요한 훅들이 적절하게 임포트되었고, 인터페이스 변경도 명확합니다.

Also applies to: 17-17, 27-27, 30-30


49-72: ⚠️ Potential issue

에러 처리 로직이 필요합니다.

메모 삭제 작업에 대한 에러 처리가 여전히 누락되어 있습니다.

다음과 같이 에러 처리를 추가해주세요:

 mutateDeleteMemo(
   memos.map(memo => memo.id),
   {
+    onError: (error) => {
+      toast({
+        title: t('toastTitle.error'),
+        description: t('toastDescription.deleteFailed'),
+        variant: 'destructive',
+      });
+      console.error('Delete failed:', error);
+    },
     onSuccess: () => {
       // ... existing success handling
     },
   },
 );

Copy link
@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: 1

🧹 Outside diff range and nitpick comments (5)
packages/web/src/app/[lng]/memos/components/MemoCardFooter/MemoOption.tsx (3)

27-28: 타입 안전성 개선이 필요합니다.

MemoRow[] 타입에 대한 추가적인 제약이 필요합니다. 빈 배열이 전달될 경우 컴포넌트의 동작이 불명확할 수 있습니다.

다음과 같이 수정을 제안합니다:

interface MemoOptionProps extends LanguageType {
-  memos: MemoRow[];
+  memos: [MemoRow, ...MemoRow[]]; // at least one memo
  closeMemoOption: () => void;
}

Line range hint 81-108: 로딩 상태 처리가 필요합니다.

카테고리 변경 중에 사용자 피드백이 없어 작업이 진행 중인지 알기 어렵습니다.

다음과 같이 수정을 제안합니다:

+ const [isUpdating, setIsUpdating] = useState(false);

  const handleCategoryChange = (categoryId: string) => {
+   setIsUpdating(true);
    mutateUpsertMemo(
      memos.map(memo => ({ ...memo, category_id: Number(categoryId) })),
      {
        onSuccess: () => {
          // ... existing success handling
        },
        onSettled: () => {
          setIsOpen(false);
+         setIsUpdating(false);
        },
      },
    );
  };

그리고 UI에서 로딩 상태를 표시:

- <Select  defaultValue={defaultCategoryId}>
+ <Select  defaultValue={defaultCategoryId} disabled={isUpdating}>

Line range hint 124-138: 접근성 개선이 필요합니다.

카테고리 선택 UI에 키보드 접근성이 부족합니다.

다음 사항들을 고려해주세요:

  1. aria-label 추가
  2. 키보드 네비게이션 지원
- <Select  defaultValue={defaultCategoryId}>
+ <Select 
+    
+   defaultValue={defaultCategoryId}
+   aria-label={t('option.changeCategory')}
+ >
packages/web/src/app/[lng]/memos/components/MemoView/MemoOptionHeader.tsx (1)

15-32: 접근성 개선이 필요합니다

헤더의 접근성을 개선하기 위해 다음 사항들을 제안드립니다:

  1. XIcon 버튼에 aria-label 추가
  2. 선택된 메모 수를 스크린 리더에게 알리기 위한 aria-live 영역 추가
-<Button variant="ghost" size="icon" >
+<Button 
+  variant="ghost" 
+  size="icon" 
+  >
+  aria-label={t('memos.close_selection')}
+>
   <XIcon className="h-6 w-6" />
 </Button>
 <div className="flex items-center gap-2 px-4">
-  <span className="text-md font-semibold">
+  <span className="text-md font-semibold" aria-live="polite">
     {t('memos.selected', { count: selectedMemos.length })}
   </span>
 </div>
packages/web/src/app/[lng]/memos/components/MemoView/MemoGrid.tsx (1)

49-49: 키보드 상호작용 개선이 필요합니다

Escape 키 외에도 다음과 같은 키보드 상호작용을 추가하면 좋을 것 같습니다:

  • Shift + 화살표 키로 여러 메모 선택
  • Ctrl/Cmd + A로 전체 선택
+const handleKeyDown = useCallback((e: KeyboardEvent) => {
+  if ((e.ctrlKey || e.metaKey) && e.key === 'a') {
+    e.preventDefault();
+    setSelectedMemos(memos);
+  }
+}, [memos]);

 useKeyboardBind({ key: 'Escape', callback: closeMemoOption });
+useEffect(() => {
+  window.addEventListener('keydown', handleKeyDown);
+  return () => window.removeEventListener('keydown', handleKeyDown);
+}, [handleKeyDown]);
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 6fcc79c and 94e10ec.

📒 Files selected for processing (3)
  • packages/web/src/app/[lng]/memos/components/MemoCardFooter/MemoOption.tsx (4 hunks)
  • packages/web/src/app/[lng]/memos/components/MemoView/MemoGrid.tsx (2 hunks)
  • packages/web/src/app/[lng]/memos/components/MemoView/MemoOptionHeader.tsx (1 hunks)
🔇 Additional comments (5)
packages/web/src/app/[lng]/memos/components/MemoCardFooter/MemoOption.tsx (1)

50-77: ⚠️ Potential issue

에러 처리 개선이 필요합니다.

메모 삭제 작업에 대한 에러 처리가 누락되어 있습니다.

다음과 같이 수정을 제안합니다:

 mutateDeleteMemo(
   memos.map(memo => memo.id),
   {
+    onError: (error) => {
+      toast({
+        title: t('toastTitle.error'),
+        description: t('toastDescription.deleteFailed'),
+        variant: 'destructive',
+      });
+      console.error('Delete failed:', error);
+    },
     onSuccess: () => {
       // ... existing success handling
     },
   },
 );
packages/web/src/app/[lng]/memos/components/MemoView/MemoOptionHeader.tsx (1)

1-14: 잘 정의된 타입과 인터페이스 구조입니다! 👍

컴포넌트의 props 인터페이스가 명확하게 정의되어 있고, 필요한 의존성들이 잘 import 되어 있습니다.

packages/web/src/app/[lng]/memos/components/MemoView/MemoGrid.tsx (3)

76-89: 🛠️ Refactor suggestion

렌더링 성능 최적화가 필요합니다

메모 아이템 렌더링 시 불필요한 at() 메서드 호출과 타입 단언을 줄일 수 있습니다.

-{items.map(
-  item =>
-    memos.at(item.key) && (
-      <MemoItem
-        key={item.key + gridKey}
-        memo={memos.at(item.key)!}
-        lng={lng}
-        isSelected={isMemoSelected(memos.at(item.key)!.id)}
-        >
-        isSelecting={isAnyMemoSelected}
-        data-grid-groupkey={item.groupKey}
-      />
-    ),
-)}
+{items.map(item => {
+  const memo = memos.at(item.key);
+  if (!memo) return null;
+  
+  return (
+    <MemoItem
+      key={item.key + gridKey}
+      memo={memo}
+      lng={lng}
+      isSelected={isMemoSelected(memo.id)}
+      >
+      isSelecting={isAnyMemoSelected}
+      data-grid-groupkey={item.groupKey}
+    />
+  );
+})}

Likely invalid or redundant comment.


36-43: ⚠️ Potential issue

타입 안전성 개선이 필요합니다

  1. Non-null assertion operator(!) 사용을 피하고 옵셔널 체이닝을 사용하세요
  2. 이벤트 타입을 더 구체적으로 정의하세요
-const handleMemoItemSelect = (event: MouseEvent<HTMLElement> | KeyboardEvent<HTMLElement>) => {
+const handleMemoItemSelect = (
+  event: MouseEvent<HTMLButtonElement> | KeyboardEvent<HTMLButtonElement>
+) => {
   event.stopPropagation();
   const id = Number(event.currentTarget.id);

   if (isMemoSelected(id)) {
     setSelectedMemos(prev => prev.filter(memo => memo.id !== id));
   } else {
-    setSelectedMemos(prev => [...prev, memos.find(memo => memo.id === id)!]);
+    const selectedMemo = memos.find(memo => memo.id === id);
+    if (selectedMemo) {
+      setSelectedMemos(prev => [...prev, selectedMemo]);
+    }
   }
 };

Likely invalid or redundant comment.


31-34: 🛠️ Refactor suggestion

메모리 누수 방지를 위한 콜백 최적화가 필요합니다

isMemoSelected 콜백이 selectedMemos 배열 전체를 의존성으로 가지고 있어 메모리 누수가 발생할 수 있습니다.

-const isMemoSelected = useCallback((id: number) => selectedMemos.some(memo => memo.id === id), [selectedMemos]);
+const isMemoSelected = useCallback(
+  (id: number) => selectedMemos.some(memo => memo.id === id),
+  [selectedMemos.length]
+);

Likely invalid or redundant comment.

@guesung guesung changed the title Feature/select memo Feature: 메모 선택 Dec 3, 2024
Copy link
@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: 1

🧹 Outside diff range and nitpick comments (6)
packages/web/src/app/[lng]/memos/components/MemoCardFooter/index.tsx (1)

1-1: 사용하지 않는 import 구문 제거 필요

MOTION_VARIANTS, AnimatePresence, motion은 현재 코드에서 사용되지 않고 있습니다. 코드 정리를 위해 제거하는 것이 좋겠습니다.

다음과 같이 수정해주세요:

-import { MOTION_VARIANTS } from '@extension/shared/constants';
...
-import { AnimatePresence, motion } from 'framer-motion';
+import { HeartIcon } from 'lucide-react';

Also applies to: 14-14

🧰 Tools
🪛 eslint

[error] 1-1: 'MOTION_VARIANTS' is defined but never used.

(@typescript-eslint/no-unused-vars)

packages/web/src/app/[lng]/memos/components/MemoView/MemoItem.tsx (3)

47-47: 메모 유효성 검사 로직을 개선해주세요.

현재 if (!memo) return null 체크는 TypeScript의 타입 시스템과 충돌할 수 있습니다. memo prop이 필수값으로 정의되어 있기 때문에 이 검사는 불필요할 수 있습니다.

-  if (!memo) return null
F438
;

27-34: 이벤트 핸들러 최적화가 필요합니다.

handleItemClick 함수에서 id 체크와 선택 로직을 분리하면 코드의 가독성과 유지보수성이 향상될 것 같습니다.

-  const handleItemClick = (event: MouseEvent<HTMLElement> | KeyboardEvent<HTMLElement>) => {
-    const id = event.currentTarget.id;
-    if (!id) return;
-
-    if (isSelecting) {
-      onSelect(event);
-      return;
-    }
-
-    searchParams.set('id', id);
-    router.push(searchParams.getUrl(), { scroll: false });
-  };
+  const handleItemClick = (event: MouseEvent<HTMLElement> | KeyboardEvent<HTMLElement>) => {
+    const id = event.currentTarget.id;
+    if (!id) return;
+    
+    isSelecting ? onSelect(event) : handleNavigation(id);
+  };
+
+  const handleNavigation = (id: string) => {
+    searchParams.set('id', id);
+    router.push(searchParams.getUrl(), { scroll: false });
+  };

14-17: 타입 정의를 더 명확하게 해주세요.

Props 인터페이스에서 이벤트 타입을 별도로 정의하면 코드의 재사용성과 가독성이 향상될 것 같습니다.

+type SelectEventType = MouseEvent<HTMLElement> | KeyboardEvent<HTMLElement>;
+
 interface MemoItemProps extends HTMLMotionProps<'article'>, LanguageType {
   memo: GetMemoResponse;
   isSelected: boolean;
   isSelecting: boolean;
-  onSelect: (e: MouseEvent<HTMLElement> | KeyboardEvent<HTMLElement>) => void;
+  onSelect: (e: SelectEventType) => void;
 }
packages/web/src/app/[lng]/memos/components/MemoView/MemoGrid.tsx (1)

32-35: 타입 안전성 개선이 필요합니다.

선택된 메모의 상태 관리가 잘 구현되어 있지만, 다음과 같은 개선사항을 제안드립니다:

  1. selectedMemos 상태의 초기값을 명시적으로 타입 지정
  2. isMemoSelected 함수의 매개변수에 대한 엄격한 타입 검사
-const [selectedMemos, setSelectedMemos] = useState<GetMemoResponse[]>([]);
+const [selectedMemos, setSelectedMemos] = useState<GetMemoResponse[]>(() => []);

-const isMemoSelected = useCallback((id: number) => selectedMemos.some(memo => memo.id === id), [selectedMemos]);
+const isMemoSelected = useCallback((id: number): boolean => selectedMemos.some(memo => memo.id === id), [selectedMemos]);
packages/web/src/app/[lng]/memos/components/MemoCardFooter/MemoOption.tsx (1)

Line range hint 81-108: 카테고리 변경 작업의 UX 개선이 필요합니다.

카테고리 일괄 변경 기능에 다음 개선사항이 필요합니다:

  1. 작업 진행 중 로딩 상태 표시
  2. 변경 작업 중 사용자 입력 방지
  3. 실패한 항목에 대한 개별 처리
+const [isUpdating, setIsUpdating] = useState(false);

 const handleCategoryChange = (categoryId: string) => {
+  setIsUpdating(true);
   mutateUpsertMemo(
     memos.map(memo => ({ ...memo, category_id: Number(categoryId) })),
     {
       onSettled: () => {
         setIsOpen(false);
+        setIsUpdating(false);
       },
     },
   );
 };
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 94e10ec and c552d6e.

📒 Files selected for processing (9)
  • packages/shared/src/constants/MotionVariants.ts (1 hunks)
  • packages/shared/src/constants/index.ts (1 hunks)
  • packages/web/src/app/[lng]/memos/components/MemoCardFooter/MemoOption.tsx (4 hunks)
  • packages/web/src/app/[lng]/memos/components/MemoCardFooter/index.tsx (5 hunks)
  • packages/web/src/app/[lng]/memos/components/MemoCardHeader/index.tsx (1 hunks)
  • packages/web/src/app/[lng]/memos/components/MemoView/MemoCalendar.tsx (2 hunks)
  • packages/web/src/app/[lng]/memos/components/MemoView/MemoGrid.tsx (2 hunks)
  • packages/web/src/app/[lng]/memos/components/MemoView/MemoItem.tsx (3 hunks)
  • packages/web/src/app/[lng]/memos/components/MemoView/MemoOptionHeader.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/web/src/app/[lng]/memos/components/MemoCardHeader/index.tsx
  • packages/web/src/app/[lng]/memos/components/MemoView/MemoOptionHeader.tsx
🧰 Additional context used
🪛 eslint
packages/web/src/app/[lng]/memos/components/MemoCardFooter/index.tsx

[error] 1-1: 'MOTION_VARIANTS' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 14-14: 'AnimatePresence' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 14-14: 'motion' is defined but never used.

(@typescript-eslint/no-unused-vars)

🔇 Additional comments (10)
packages/web/src/app/[lng]/memos/components/MemoCardFooter/index.tsx (4)

22-24: Props 이름 변경이 적절합니다

isHovered에서 isOptionShown으로의 변경은 prop의 목적을 더 명확하게 표현하고 있습니다. 이는 코드의 가독성을 향상시키는 좋은 변경사항입니다.


104-104: 메모 배치 처리 기능 개선이 적절합니다

MemoOption 컴포넌트에 단일 메모 ID 대신 메모 배열을 전달하도록 변경한 것은 배치 처리 기능을 지원하기 위한 적절한 개선입니다.


92-92: 상태에 따른 투명도 처리가 개선되었습니다

isOptionShown 상태에 따른 투명도 처리가 명확하게 구현되어 있습니다.


40-40: 라우터 동작 변경 검증 필요

router.replace()에서 router.push()로의 변경은 브라우저 히스토리 동작에 영향을 미칩니다. 이 변경이 의도적인지, 그리고 다른 페이지에서의 네비게이션 동작과 일관성이 있는지 확인이 필요합니다.

✅ Verification successful

라우터 동작 변경이 일관성 있게 구현되어 있음을 확인

코드베이스 분석 결과, router.push()router.replace()의 사용이 명확한 패턴을 보여줍니다:

  • 메모 관련 뷰 상태 변경(필터링, 카테고리 선택 등)은 대부분 router.push()를 사용
  • 모달이나 토글과 같은 임시 UI 상태 변경은 router.replace()를 사용

따라서 MemoCardFooter에서 router.push()를 사용하는 것은 메모 뷰의 상태 변경을 위한 일관된 패턴을 따르고 있습니다.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: 다른 컴포넌트에서의 라우터 사용 패턴 확인

# router.push() 사용 패턴 검색
rg "router\.(push|replace)" --type tsx

Length of output: 68


Script:

#!/bin/bash
# Description: 라우터 사용 패턴 검색 (파일 확장자 수정)

# router.push() 및 router.replace() 사용 패턴 검색
rg "router\.(push|replace)" -t ts -t tsx -t jsx -t js

Length of output: 83


Script:

#!/bin/bash
# Description: 라우터 사용 패턴 검색 (확장자 없이 전체 검색)

# router.push() 및 router.replace() 사용 패턴 검색
rg "router\.(push|replace)" --type-add 'web:*.{ts,tsx,js,jsx}' -t web

Length of output: 1089

packages/shared/src/constants/MotionVariants.ts (1)

1-8: 애니메이션 상수가 잘 정의되어 있습니다!

애니메이션 지속 시간과 투명도, y축 변환이 적절하게 설정되어 있으며, 중앙 집중식 관리를 통해 일관성 있는 모션을 구현할 수 있습니다.

packages/shared/src/constants/index.ts (1)

6-6: 내보내기가 알파벳 순서대로 잘 정렬되어 있습니다!

MotionVariants 내보내기가 기존 패턴을 따르고 있으며 알파벳 순서도 잘 지켜졌습니다.

packages/web/src/app/[lng]/memos/components/MemoView/MemoGrid.tsx (2)

81-94: 🛠️ Refactor suggestion

성능 최적화가 필요합니다.

MasonryInfiniteGrid 컴포넌트 내부의 렌더링 로직에서 다음과 같은 성능 개선이 필요합니다:

  1. 중복된 memos.at() 호출 제거
  2. 불필요한 널 체크 최소화

다음과 같이 수정을 제안합니다:

{items.map(item => {
-  memos.at(item.key) && (
+  const memo = memos.at(item.key);
+  return memo && (
    <MemoItem
      key={item.key + gridKey}
-     memo={memos.at(item.key)!}
+     memo={memo}
      lng={lng}
-     isSelected={isMemoSelected(memos.at(item.key)!.id)}
+     isSelected={isMemoSelected(memo.id)}
      
      isSelecting={isAnyMemoSelected}
      data-grid-groupkey={item.groupKey}
    />
  )
})}

Likely invalid or redundant comment.


37-44: ⚠️ Potential issue

안전하지 않은 타입 단언 제거가 필요합니다.

handleMemoItemSelect 함수에서 발견된 잠재적인 문제점들:

  1. 널 아님 단언 연산자(!) 사용
  2. 메모를 찾지 못했을 경우의 예외 처리 부재

다음과 같이 수정을 제안합니다:

-else setSelectedMemos(prev => [...prev, memos.find(memo => memo.id === id)!]);
+else {
+  const selectedMemo = memos.find(memo => memo.id === id);
+  if (selectedMemo) {
+    setSelectedMemos(prev => [...prev, selectedMemo]);
+  } else {
+    console.error(`메모를 찾을 수 없습니다: ${id}`);
+  }
+}

Likely invalid or redundant comment.

packages/web/src/app/[lng]/memos/components/MemoView/MemoCalendar.tsx (1)

53-53: 브라우저 히스토리 동작 변경에 대한 검토가 필요합니다.

router.replace에서 router.push로의 변경은 브라우저의 뒤로 가기 동작에 영향을 미칩니다. 이는 사용자 경험에 중요한 변화를 가져올 수 있습니다.

✅ Verification successful

Let me analyze the router usage patterns across the codebase to make a final determination about the change from router.replace to router.push.

From the results, I can see that:

  1. router.push is predominantly used for navigation with search params updates (MemoItem, MemoCardFooter, MemoCalendar)
  2. router.replace is used in dialog/toggle components (MemoDialog, ToggleView)
  3. The change in MemoCalendar aligns with the pattern used in similar components

I can now generate the final response:

router.push 사용은 적절해 보입니다

메모 관련 컴포넌트들의 라우터 사용 패턴을 분석한 결과, router.push로의 변경이 타당합니다:

  • 메모 뷰 컴포넌트들(MemoItem, MemoCardFooter 등)은 일관되게 router.push를 사용
  • router.replace는 주로 다이얼로그나 토글과 같은 임시 UI 상태 변경에만 사용
  • 캘린더를 통한 날짜 선택은 의미있는 네비게이션 히스토리로 간주되어야 함
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# 라우터 사용 패턴 확인
rg "router\.(push|replace)" -A 2

Length of output: 2273

packages/web/src/app/[lng]/memos/components/MemoCardFooter/MemoOption.tsx (1)

47-77: ⚠️ Potential issue

에러 처리 및 사용자 피드백 개선이 필요합니다.

메모 삭제 기능에 다음 개선사항이 필요합니다:

  1. 삭제 실패 시 에러 처리
  2. 사용자에게 적절한 피드백 제공
  3. 네트워크 오류 처리

다음과 같이 수정을 제안합니다:

 mutateDeleteMemo(
   memos.map(memo => memo.id),
   {
+    onError: (error) => {
+      toast({
+        title: t('toastTitle.error'),
+        description: t('toastDescription.deleteFailed'),
+        variant: 'destructive',
+      });
+      console.error('삭제 실패:', error);
+    },
     onSuccess: () => {
       // ... existing success handling
     },
   },
 );

Likely invalid or redundant comment.

Comment on lines +51 to +54
variants={MOTION_VARIANTS.fadeInAndOut}
initial="initial"
animate="animate"
exit="exit"
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

접근성과 성능 개선이 필요합니다.

  1. 접근성:

    • 선택 상태를 나타내는 aria-selected 속성 추가 필요
    • 스크린 리더를 위한 aria-label 추가 필요
  2. 성능:

    • motion 컴포넌트의 불필요한 리렌더링을 방지하기 위해 whileFocus 속성 추가 고려
    • isHovered 상태 변경 시 발생하는 리렌더링 최적화 필요
 <motion.article
   id={String(memo.id)}
   variants={MOTION_VARIANTS.fadeInAndOut}
   initial="initial"
   animate="animate"
   exit="exit"
+  whileFocus={{ scale: 1.02 }}
   
    => e.key === 'Enter' && handleItemClick(e)}
   className="transition-all"
   tabIndex={0}
+  aria-selected={isSelected}
+  aria-label={`메모: ${memo.title || '제목 없음'}`}
   {...props}>
   <Card
     className={cn('relative box-content w-[300px] transition-all', {
       'border-primary cursor-pointer': isSelected,
     })}
     
     >

Also applies to: 60-68

@guesung guesung merged commit 331e160 into develop Dec 3, 2024
8 checks passed
@guesung guesung deleted the feature/select-memo branch December 3, 2024 12:34
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.

1 participant
0