-
Notifications
You must be signed in to change notification settings - Fork 1
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
Feature: 메모 선택 #113
Conversation
Walkthrough이 풀 리퀘스트는 메모의 삽입 및 업데이트를 위한 새로운 훅 Changes
Possibly related PRs
📜 Recent review detailsConfiguration used: .coderabbit.yaml 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
🔇 Additional comments (1)packages/web/src/app/[lng]/memos/components/MemoDialog/index.tsx (1)
다음 스크립트를 실행하여 prop 이름 변경이 전체 코드베이스에서 일관되게 적용되었는지 확인하세요: ✅ Verification successfulprop 이름이 일관되게 변경되었으며 올바르게 사용되고 있습니다.
🏁 Scripts executedThe 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? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
Documentation and Community
|
There was a problem hiding this 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
: 선택 버튼의 접근성 개선이 필요합니다.선택 버튼이 적절하게 구현되었지만, 다음 사항들을 개선하면 좋을 것 같습니다:
- 스크린 리더 사용자를 위한 aria-label 추가
- 선택 상태를 나타내는 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
: 성능 최적화를 위한 제안
onMouseEnter
와onMouseLeave
핸들러를 메모이제이션하면 불필요한 리렌더링을 방지할 수 있습니다.+const handleMouseEnter = useCallback(() => { + setIsHovered(true); +}, []); + +const handleMouseLeave = useCallback(() => { + setIsHovered(false); +}, []);packages/web/src/app/[lng]/memos/components/MemoView/MemoGrid.tsx (1)
56-65
: 무한 스크롤 로직의 성능 최적화가 필요합니다.현재 구현은 기능적으로는 문제가 없지만, 다음과 같은 최적화를 고려해보시면 좋을 것 같습니다:
- 불필요한 배열 복사 최소화
- 계산 로직 메모이제이션
+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
📒 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
packages/web/src/app/[lng]/memos/components/MemoView/MemoOptionHeader.tsx
Outdated
Show resolved
Hide resolved
packages/web/src/app/[lng]/memos/components/MemoView/MemoGrid.tsx
Outdated
Show resolved
Hide resolved
packages/web/src/app/[lng]/memos/components/MemoView/MemoGrid.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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
: 카테고리 변경 시 유효성 검사가 필요합니다.카테고리 변경 작업에서 다음과 같은 유효성 검사가 추가되면 좋을 것 같습니다:
- 카테고리 ID의 유효성 검사
- 빈 메모 배열 처리
- 중복 요청 방지
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
: 접근성 개선이 필요합니다.백스페이스 키 바인딩에 대한 사용자 안내가 필요합니다. 다음 사항들을 고려해주세요:
- 키보드 단축키에 대한 툴팁 추가
- 스크린 리더 사용자를 위한 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
📒 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
훅을 사용하여 키보드 이벤트 리스너의 등록과 해제가 올바르게 구현되었습니다. 의존성 배열에 key
와 callback
을 포함하여 변경 시마다 효과가 재실행되도록 한 부분도 좋습니다.
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
:
불변성 유지와 새로운 메모 추가를 위한 로직 개선 필요
현재 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
메모 선택 로직 개선이 필요합니다.
이전 리뷰에서 제기된 문제가 아직 해결되지 않았습니다. 선택 로직에서 다음과 같은 개선이 필요합니다:
- 타입 안전성을 위해
!
연산자 사용을 피하고 옵셔널 체이닝을 사용 - 메모 검색 성능 최적화를 위한 캐싱 구현
-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
메모 렌더링 최적화가 필요합니다.
이전 리뷰에서 제기된 성능 문제가 아직 해결되지 않았습니다:
- 불필요한
memo.at()
호출 최소화 - 선택 상태 확인 로직 최적화
+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
훅을 사용한 구현이 깔끔하고 재사용성이 높습니다.
packages/web/src/app/[lng]/memos/components/MemoCardFooter/MemoOption.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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 구현이 개선될 수 있습니다현재 구현은 기능적으로는 문제가 없으나, 다음과 같은 개선사항을 고려해보세요:
- 버튼의 위치가 절대 위치로 지정되어 있어 컨테이너 크기 변경 시 문제가 될 수 있습니다.
- 접근성을 위해 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
: 메모 업데이트 로직이 개선되었습니다이전 리뷰 의견이 잘 반영되어 다음과 같은 개선이 이루어졌습니다:
- 새로운 메모 추가 시
unshift
를 사용하여 목록 최상단에 추가- 기존 메모 업데이트 시 불변성을 유지하면서 처리
다만, 타입 안전성을 위해 다음과 같은 개선을 제안합니다:
- 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
: 에러 처리가 중복되어 있습니다
previousMemos
와previousMemosData
에 대한 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
📒 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
:
에러 처리 로직이 필요합니다.
메모 삭제 작업에 대한 에러 처리가 여전히 누락되어 있습니다.
다음과 같이 에러 처리를 추가해주세요:
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/MemoCardFooter/MemoOption.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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에 키보드 접근성이 부족합니다.
다음 사항들을 고려해주세요:
aria-label
추가- 키보드 네비게이션 지원
- <Select defaultValue={defaultCategoryId}> + <Select + + defaultValue={defaultCategoryId} + aria-label={t('option.changeCategory')} + >packages/web/src/app/[lng]/memos/components/MemoView/MemoOptionHeader.tsx (1)
15-32
: 접근성 개선이 필요합니다헤더의 접근성을 개선하기 위해 다음 사항들을 제안드립니다:
- XIcon 버튼에 aria-label 추가
- 선택된 메모 수를 스크린 리더에게 알리기 위한 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
📒 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
:
에러 처리 개선이 필요합니다.
메모 삭제 작업에 대한 에러 처리가 누락되어 있습니다.
다음과 같이 수정을 제안합니다:
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
:
타입 안전성 개선이 필요합니다
- Non-null assertion operator(!) 사용을 피하고 옵셔널 체이닝을 사용하세요
- 이벤트 타입을 더 구체적으로 정의하세요
-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.
packages/web/src/app/[lng]/memos/components/MemoCardFooter/MemoOption.tsx
Show resolved
Hide resolved
There was a problem hiding this 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
: 타입 안전성 개선이 필요합니다.선택된 메모의 상태 관리가 잘 구현되어 있지만, 다음과 같은 개선사항을 제안드립니다:
selectedMemos
상태의 초기값을 명시적으로 타입 지정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 개선이 필요합니다.카테고리 일괄 변경 기능에 다음 개선사항이 필요합니다:
- 작업 진행 중 로딩 상태 표시
- 변경 작업 중 사용자 입력 방지
- 실패한 항목에 대한 개별 처리
+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
📒 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
컴포넌트 내부의 렌더링 로직에서 다음과 같은 성능 개선이 필요합니다:
- 중복된
memos.at()
호출 제거 - 불필요한 널 체크 최소화
다음과 같이 수정을 제안합니다:
{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
:
안전하지 않은 타입 단언 제거가 필요합니다.
handleMemoItemSelect
함수에서 발견된 잠재적인 문제점들:
- 널 아님 단언 연산자(
!
) 사용 - 메모를 찾지 못했을 경우의 예외 처리 부재
다음과 같이 수정을 제안합니다:
-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:
router.push
is predominantly used for navigation with search params updates (MemoItem, MemoCardFooter, MemoCalendar)router.replace
is used in dialog/toggle components (MemoDialog, ToggleView)- 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
:
에러 처리 및 사용자 피드백 개선이 필요합니다.
메모 삭제 기능에 다음 개선사항이 필요합니다:
- 삭제 실패 시 에러 처리
- 사용자에게 적절한 피드백 제공
- 네트워크 오류 처리
다음과 같이 수정을 제안합니다:
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.
variants={MOTION_VARIANTS.fadeInAndOut} | ||
initial="initial" | ||
animate="animate" | ||
exit="exit" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
접근성과 성능 개선이 필요합니다.
-
접근성:
- 선택 상태를 나타내는 aria-selected 속성 추가 필요
- 스크린 리더를 위한 aria-label 추가 필요
-
성능:
- 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
Summary by CodeRabbit
새로운 기능
useMemosUpsertMutation
훅 추가.useDeleteMemosMutation
훅 추가.MemoOptionHeader
컴포넌트 추가.MemoGrid
및MemoItem
컴포넌트 수정.useKeyboardBind
훅 추가.버그 수정
문서화
isAllSame
추가.Summary by CodeRabbit
새로운 기능
useMemosUpsertMutation
훅 추가.useDeleteMemosMutation
훅 추가.MOTION_VARIANTS
상수 추가.MemoOptionHeader
컴포넌트 추가.버그 수정
문서화
리팩토링