Conversation
Walkthrough카드 편집 모달(ChangeCardModal) 추가 및 CardDetailModal 편집·업데이트 흐름 통합, Combobox 타입·컨텍스트 확장, 카드 관련 타입·API·유틸(updateCardRequestBody) 추가, 여러 컴포넌트의 prop 시그니처와 렌더링 로직이 변경되었습니다. (50단어 이내) Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 주의가 필요한 영역:
Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/dashboard/combobox/Combobox.tsx (1)
47-48: 불필요한 상태 제거
selectedNode와setSelectedNode는useComboboxState훅에서 생성되고 Combobox 컴포넌트에서 context에 포함되지만, ComboboxContextType 인터페이스에 정의되지 않아 자식 컴포넌트에서 접근할 수 없습니다. 실제 사용되지 않는 dead code이므로 다음에서 제거해주세요:
useComboboxState.ts에서 상태 정의 및 반환값 제거Combobox.tsx47-48줄에서 구조 분해 제거Combobox.tsx59-60줄 및 70줄의 context 포함 제거
🧹 Nitpick comments (6)
src/types/card.ts (1)
1-2: 타입 파일에서 컴포넌트 파일 임포트는 순환 의존성 위험이 있습니다.
UserComboboxValue타입을 컴포넌트 파일(@/components/...)에서 임포트하고 있습니다. 타입 정의는 별도의 types 파일로 분리하여 순환 의존성을 방지하는 것이 좋습니다.
UserComboboxValue타입을src/types/combobox.ts같은 별도 파일로 이동하는 것을 권장합니다:// src/types/combobox.ts export type UserComboboxValue = { id: number; nickname: string; profileImageUrl: string | null; }; export type StatusComboboxValue = { id: number; title: string; };src/components/dashboard/combobox/ComboboxTrigger.tsx (2)
54-82: 타입 가드 사용으로 타입 안전성 개선 권장
'nickname' in selectedValue와'title' in selectedValue로 타입을 구분하고 있습니다. 현재는 동작하지만, 추후 타입이 확장되면 예상치 못한 동작이 발생할 수 있습니다.타입 가드 함수를 사용하면 더 명확해집니다:
// utils 또는 상단에 정의 const isUserValue = (value: unknown): value is UserComboboxValue => typeof value === 'object' && value !== null && 'nickname' in value; const isStatusValue = (value: unknown): value is StatusComboboxValue => typeof value === 'object' && value !== null && 'title' in value;
80-82: 예상치 못한 값에 대한 fallback 처리 확인 필요
String(selectedValue)는 객체일 경우"[object Object]"를 렌더링할 수 있습니다. 현재UserComboboxValue와StatusComboboxValue만 사용된다면 이 분기에 도달하지 않겠지만, 방어적 로깅을 추가하면 디버깅에 도움이 됩니다.} else { + console.warn('ComboboxTrigger: 알 수 없는 selectedValue 타입', selectedValue); valueContent = <span className='font-medium'>{String(selectedValue)}</span>; }src/components/dashboard-detail/modal/CreateCardModal.tsx (2)
9-12: 불필요한 타입 import
StatusComboboxValue는 이 컴포넌트에서 사용되지 않습니다. 담당자(assigneeUser)만 처리하므로UserComboboxValue만 필요합니다.import Combobox, { - type StatusComboboxValue, type UserComboboxValue, } from '@/components/dashboard/combobox/Combobox';
45-49: handleChange 타입 간소화 고려현재
StatusComboboxValue가 union에 포함되어 있지만, 이 컴포넌트에서는assigneeUser필드만 Combobox를 사용하므로 실제로StatusComboboxValue가 전달될 일이 없습니다. 타입 정확성을 높이려면UserComboboxValue | null로 좁힐 수 있습니다.src/components/dashboard-detail/modal/ChangeCardModal.tsx (1)
12-12: 미사용 import 확인
DUE_DATE와IMAGE_URL이 import되었지만, 조건 비교에만 사용됩니다. 실제로 필요한지 검토해주세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
src/components/common/avatar/Avatar.tsx(1 hunks)src/components/dashboard-detail/card/ColumnCardList.tsx(4 hunks)src/components/dashboard-detail/card/DashboardCard.tsx(2 hunks)src/components/dashboard-detail/modal/ChangeCardModal.tsx(1 hunks)src/components/dashboard-detail/modal/CreateCardModal.tsx(2 hunks)src/components/dashboard-detail/modal/TagInput.tsx(1 hunks)src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx(4 hunks)src/components/dashboard/combobox/Combobox.tsx(1 hunks)src/components/dashboard/combobox/ComboboxItem.tsx(1 hunks)src/components/dashboard/combobox/ComboboxList.tsx(1 hunks)src/components/dashboard/combobox/ComboboxTrigger.tsx(4 hunks)src/context/comboboxContext.ts(1 hunks)src/lib/apis/cards.ts(3 hunks)src/pages/DashboardDetail.tsx(1 hunks)src/types/card.ts(3 hunks)src/utils/card/updateCardRequestBody.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.ts
⚙️ CodeRabbit configuration file
**/*.ts: language: "ko-KR"reviews:
instructions: |
우리 팀은 다음의 코드 스타일 가이드를 따릅니다:[기본 규칙] - 변수는 항상 const 우선 사용 - 파일명은 camelCase로 작성 - 필요 없는 코드는 즉시 제거 (미사용 변수/함수 금지) - 함수를 제외한 유틸 파일은 default export 대신 named export 권장 [함수 네이밍 규칙] - 모든 함수는 **동사 + 목적어** 형태로 작성 - 데이터 조회 함수: get / fetch / load - 데이터 수정 함수: set / update / save / remove - 이벤트 함수: handle 사용 - 계산 / 변환 함수: calculate / convert / parse / format - 조건 검사 함수: is / has / can [디렉토리 & 파일 구조] - 디렉토리명은 kebab-case 사용 - 에셋(icon, image, logos)은 index 파일을 통한 일괄 export - import 시 `@/assets/icons` 형태로 그룹 단위 import [상수 / 환경 변수] - SNAKE_CASE 사용 - 예: PAGE_SIZE, API_URL [주석 & 문서화] - TODO 주석 적극 활용 (급한 PR / 미구현 영역) - TSDoc 사용 가능 (컴포넌트 설명, 사용 예시 추가) - @param은 TypeScript 타입으로 설명하기 때문에 비사용
Files:
src/utils/card/updateCardRequestBody.tssrc/types/card.tssrc/lib/apis/cards.tssrc/context/comboboxContext.ts
**/*.tsx
⚙️ CodeRabbit configuration file
**/*.tsx: reviews:
instructions: |
우리 팀은 다음의 코드 스타일 가이드를 따릅니다:[기본 규칙] - 변수는 const 우선 사용 - 파일명은 PascalCase로 작성 - 컴포넌트는 default export를 사용 - 파일명과 컴포넌트명은 반드시 일치 (PascalCase) - 불필요한 props는 전달하지 않기 - UI 관련 상태는 명확한 이름 사용 (isOpen, isLoading 등) [함수 네이밍 규칙] - 모든 함수는 **동사 + 목적어** - UI 이벤트 핸들러는 handle* 사용 (handleClick, handleToggle 등) - 내부 로직은 calculate / convert / format 등 의미 기반 네이밍 사용 [디렉토리 & 파일 구조] - 디렉토리는 kebab-case - 컴포넌트 파일은 PascalCase - assets는 index 기반 export (Icons, Images, Logos 등) - import 시 그룹 단위 import 사용 [상수 / 환경 변수] - SNAKE_CASE 사용 - 예: PAGE_SIZE, API_URL [함수 선언 규칙] - 컴포넌트는 선언식 function Component() 형태 - 컴포넌트 내부의 로직 함수는 화살표 함수 사용 - 유틸성 컴포넌트가 아니면 default export [주석 & 문서화] - TODO 주석 사용 (미완성 코드 표시) - TSDoc을 컴포넌트 단위로 활용 - 예시를 포함한 간단한 설명 추가
Files:
src/components/dashboard/combobox/Combobox.tsxsrc/components/dashboard-detail/modal/TagInput.tsxsrc/components/common/avatar/Avatar.tsxsrc/components/dashboard/combobox/ComboboxItem.tsxsrc/pages/DashboardDetail.tsxsrc/components/dashboard-detail/modal/ChangeCardModal.tsxsrc/components/dashboard-detail/card/ColumnCardList.tsxsrc/components/dashboard/combobox/ComboboxList.tsxsrc/components/dashboard/combobox/ComboboxTrigger.tsxsrc/components/dashboard-detail/modal/CreateCardModal.tsxsrc/components/dashboard-detail/card/DashboardCard.tsxsrc/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx
🧠 Learnings (7)
📚 Learning: 2025-11-30T15:48:53.701Z
Learnt from: aahreum
Repo: sprint-19-part3-7team/Taskify-Frontend PR: 283
File: src/utils/card/createCardReqBody.ts:20-30
Timestamp: 2025-11-30T15:48:53.701Z
Learning: In src/utils/card/createCardReqBody.ts, the backend requires a default IMAGE_URL fallback (`imageUrl ?? IMAGE_URL`) when no image is provided, rather than accepting null. This is part of the server API contract, similar to how it requires assigneeUserId: 0 and tags: [''] as default values.
Applied to files:
src/utils/card/updateCardRequestBody.tssrc/lib/apis/cards.ts
📚 Learning: 2025-11-30T15:48:27.926Z
Learnt from: aahreum
Repo: sprint-19-part3-7team/Taskify-Frontend PR: 283
File: src/utils/card/createCardReqBody.ts:17-18
Timestamp: 2025-11-30T15:48:27.926Z
Learning: In src/utils/card/createCardReqBody.ts, the backend requires tags to be `['']` (an array containing one empty string) when no tags are provided, not an empty array `[]`. This is a server-side requirement.
Applied to files:
src/utils/card/updateCardRequestBody.tssrc/components/dashboard-detail/modal/TagInput.tsx
📚 Learning: 2025-11-30T15:48:27.926Z
Learnt from: aahreum
Repo: sprint-19-part3-7team/Taskify-Frontend PR: 283
File: src/utils/card/createCardReqBody.ts:17-18
Timestamp: 2025-11-30T15:48:27.926Z
Learning: In src/utils/card/createCardReqBody.ts, the backend accepts `assigneeUserId: 0` as a valid value when no assignee is selected and no currentUserId is provided. This is part of the server API contract.
Applied to files:
src/utils/card/updateCardRequestBody.tssrc/types/card.ts
📚 Learning: 2025-11-29T18:56:30.435Z
Learnt from: aahreum
Repo: sprint-19-part3-7team/Taskify-Frontend PR: 265
File: src/pages/DashboardDetail.tsx:106-116
Timestamp: 2025-11-29T18:56:30.435Z
Learning: In src/pages/DashboardDetail.tsx, column names (title field) are allowed to contain leading or trailing whitespace. Trimming whitespace from column names is not required.
Applied to files:
src/components/dashboard-detail/modal/TagInput.tsxsrc/pages/DashboardDetail.tsxsrc/components/dashboard-detail/card/ColumnCardList.tsxsrc/components/dashboard-detail/card/DashboardCard.tsx
📚 Learning: 2025-11-22T16:39:21.177Z
Learnt from: aahreum
Repo: sprint-19-part3-7team/Taskify-Frontend PR: 93
File: src/components/dashboard/dropdown/DropdownMenuSearchTrigger.tsx:32-63
Timestamp: 2025-11-22T16:39:21.177Z
Learning: In the DropdownMenuSearchTrigger component (src/components/dashboard/dropdown/DropdownMenuSearchTrigger.tsx), a div element is intentionally used instead of a button because the component conditionally renders an input element when open, and nesting input inside button is invalid HTML.
Applied to files:
src/components/dashboard/combobox/ComboboxTrigger.tsx
📚 Learning: 2025-11-23T09:21:58.302Z
Learnt from: aahreum
Repo: sprint-19-part3-7team/Taskify-Frontend PR: 93
File: src/context/dropdownContext.ts:6-21
Timestamp: 2025-11-23T09:21:58.302Z
Learning: src/context/dropdownContext.ts의 BasicDropdownContextType과 SearchableDropdownContextType 인터페이스는 파일 내부에서만 사용되며, useDropdownContext.ts의 훅들이 useContext()를 통해 자동으로 타입을 추론하므로 export가 필요하지 않습니다.
Applied to files:
src/context/comboboxContext.ts
📚 Learning: 2025-12-01T01:09:10.680Z
Learnt from: aahreum
Repo: sprint-19-part3-7team/Taskify-Frontend PR: 287
File: src/lib/apis/Invitations.ts:31-35
Timestamp: 2025-12-01T01:09:10.680Z
Learning: In this codebase, mutation functions (POST/PUT/DELETE) return the full response object (`res`) instead of `res.data`, because `useMutation` internally processes `res.data`. Only GET methods return `res.data` directly.
Applied to files:
src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx
🧬 Code graph analysis (10)
src/utils/card/updateCardRequestBody.ts (4)
src/types/card.ts (2)
CardEditFormValue(39-47)CardDetailResponse(10-22)src/lib/apis/cards.ts (1)
UpdateCardType(3-11)src/utils/formatDateTime.ts (1)
formatDueDate(19-33)src/constants/requestCardData.ts (2)
DUE_DATE(2-2)IMAGE_URL(3-4)
src/components/dashboard-detail/modal/TagInput.tsx (2)
src/components/dashboard/Tag.tsx (1)
Tag(37-48)src/utils/avatar.ts (1)
getProfileColorForId(19-22)
src/components/common/avatar/Avatar.tsx (1)
src/utils/cn.ts (1)
cn(5-7)
src/components/dashboard/combobox/ComboboxItem.tsx (1)
src/components/dashboard/combobox/Combobox.tsx (2)
UserComboboxValue(8-13)StatusComboboxValue(15-18)
src/components/dashboard-detail/card/ColumnCardList.tsx (2)
src/types/column.ts (2)
ColumnsData(1-8)ColumnsResponse(10-13)src/types/card.ts (1)
CardDetailResponse(10-22)
src/components/dashboard/combobox/ComboboxList.tsx (2)
src/components/dashboard/combobox/ComboboxItem.tsx (1)
ComboboxItemProps(8-11)src/utils/combobox.ts (1)
matchSearch(7-12)
src/components/dashboard/combobox/ComboboxTrigger.tsx (2)
src/components/common/avatar/Avatar.tsx (1)
Avatar(25-44)src/components/dashboard-detail/card/CardStatusBadge.tsx (1)
CardStatusBadge(8-19)
src/components/dashboard-detail/modal/CreateCardModal.tsx (1)
src/components/dashboard/combobox/Combobox.tsx (2)
UserComboboxValue(8-13)StatusComboboxValue(15-18)
src/context/comboboxContext.ts (1)
src/components/dashboard/combobox/Combobox.tsx (2)
UserComboboxValue(8-13)StatusComboboxValue(15-18)
src/components/dashboard-detail/card/DashboardCard.tsx (3)
src/types/card.ts (1)
CardDetailResponse(10-22)src/types/column.ts (1)
ColumnsResponse(10-13)src/types/members.ts (1)
MembersResponse(12-15)
🪛 GitHub Actions: Preview
src/components/dashboard-detail/modal/ChangeCardModal.tsx
[error] 92-92: TS2322: Type 'ColumnsData | undefined' is not assignable to type 'UserComboboxValue | StatusComboboxValue | null'. Type 'undefined' is not assignable to type 'UserComboboxValue | StatusComboboxValue | null'.
[error] 93-93: TS18047: 'colObj' is possibly 'null'.
[error] 93-93: TS2345: Argument of type 'number | undefined' is not assignable to parameter of type 'string | Assignee | null'. Type 'undefined' is not assignable to type 'string | Assignee | null'.
[error] 112-112: TS2322: Type '(value: string | Assignee | null) => void' is not assignable to type '(value: UserComboboxValue | StatusComboboxValue | null) => void'. Types of parameters 'value' are incompatible. Type 'StatusComboboxValue' is not assignable to type 'string | Assignee | null'.
🔇 Additional comments (20)
src/components/common/avatar/Avatar.tsx (1)
27-30: LGTM!
shrink-0추가는 flex 컨테이너 내에서 Avatar가 축소되지 않도록 보장합니다. Combobox나 카드 목록 등 공간이 제한된 레이아웃에서 Avatar 크기가 일관되게 유지됩니다.src/components/dashboard-detail/card/DashboardCard.tsx (2)
13-21: LGTM!새로운 props가 명확한 타입과 함께 잘 정의되었습니다.
onUpdateCard핸들러 네이밍이 코딩 가이드라인(동사 + 목적어)을 따르고 있습니다.
80-91: LGTM!CardDetailModal에 필요한 props가 올바르게 전달되고 있습니다. 모달이 조건부 렌더링되어 불필요한 마운트를 방지합니다.
src/types/card.ts (1)
30-47:assigneeUser타입 불일치 확인 필요
CardInitialValueType.assigneeUser는UserComboboxValue | null을 사용하고,CardEditFormValue.assigneeUser는Assignee | null을 사용합니다. 의도된 설계라면 괜찮지만, 두 인터페이스 간 변환 시 혼란을 야기할 수 있습니다.두 타입의 용도가 다른지 확인해주세요:
CardInitialValueType: Combobox에서 사용하는 초기값CardEditFormValue: API 요청에 사용하는 폼 값의도적인 분리라면 주석으로 용도를 명시하면 좋겠습니다.
src/components/dashboard-detail/modal/TagInput.tsx (1)
51-58: LGTM!빈 문자열 태그를 필터링하여 UI에 표시하지 않는 로직이 적절합니다. 백엔드에서
['']를 요구하는 경우에도 UI에서는 깔끔하게 처리됩니다.src/components/dashboard/combobox/ComboboxTrigger.tsx (1)
59-73: LGTM!Avatar 컴포넌트에 전달되는 user 객체가
Assignee타입 구조와 일치합니다.shrink-0클래스가 적용된 Avatar와 함께 flex 레이아웃에서 안정적으로 렌더링됩니다.src/components/dashboard/combobox/ComboboxList.tsx (1)
13-25: LGTM!
nickname과title속성을 체크하여UserComboboxValue와StatusComboboxValue를 적절히 처리하는 필터링 로직입니다.in연산자를 통한 타입 좁히기가 잘 적용되었습니다.src/pages/DashboardDetail.tsx (1)
182-192: LGTM!카드 수정 모달에서 상태(컬럼) 선택을 지원하기 위해
columnListDataprop을 추가하여ColumnCardList로 전달하는 변경입니다. 데이터 흐름이 명확합니다.src/lib/apis/cards.ts (1)
3-15: LGTM!
ChangeCardType을UpdateCardType으로 명확하게 리네이밍하고 export했습니다.CreateCardType이UpdateCardType을 확장하는 구조가 적절합니다.src/context/comboboxContext.ts (2)
2-5: LGTM!Combobox 컴포넌트에서
UserComboboxValue와StatusComboboxValue타입을 가져와 context 타입을 확장하는 방식이 적절합니다.
11-12: Union 타입 적용이 적절합니다.
selectedValue가UserComboboxValue | StatusComboboxValue | null타입을 사용하여 사용자 선택과 상태 선택 두 가지 Combobox 케이스를 모두 처리할 수 있게 되었습니다.src/components/dashboard-detail/card/ColumnCardList.tsx (4)
11-14: LGTM!타입 import가 적절하게 업데이트되었습니다.
CardDetailResponse와ColumnsResponse추가로 카드 수정 기능을 지원합니다.
17-23: Props 인터페이스 확장이 적절합니다.
columnListDataprop 추가로 하위 컴포넌트에서 컬럼 정보를 활용할 수 있게 되었습니다.
111-121: LGTM!
handleUpdateCard함수가 불변성을 유지하면서 카드를 업데이트합니다.map을 사용한 immutable update 패턴이 적절합니다.
153-161: LGTM!
DashboardCard에onUpdateCard,memberData,columnListDataprops를 전달하여 카드 수정 플로우를 지원합니다.src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx (5)
6-19: LGTM!카드 수정 기능을 위한 import가 적절하게 추가되었습니다.
ChangeCardModal,updateCardAPI, 관련 타입 및 유틸리티 함수들이 포함되었습니다.
35-44: Props 인터페이스가 명확합니다.
CardDetailModalProps인터페이스에onUpdateCard,memberData,columnListData가 추가되어 카드 수정 플로우를 지원합니다.
59-60: 두 개의 모달 컨트롤러 패턴이 적절합니다.
editModal과detailModal을 분리하여 상세 보기와 수정 모드를 독립적으로 관리합니다. 고유한 modal name을 사용하여 충돌을 방지합니다.
70-85: Mutation 정의가 적절합니다.
deleteCardMutation과updateCardMutation이 적절하게 분리되어 있습니다.updateCardMutation의onSuccess에서refetch와 모달 전환 로직이 올바르게 구현되었습니다.
166-183: ChangeCardModal 렌더링이 적절합니다.
editModal.isOpen과cardDetailQuery.data조건을 통해 데이터가 준비된 상태에서만 수정 모달을 렌더링합니다.initialValue로 기존 카드 데이터를 전달하는 패턴이 올바릅니다.
테스트 진행중
|
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
src/components/dashboard-detail/modal/ChangeCardModal.tsx (1)
97-99: 컴포넌트가 암시적으로 undefined 반환
if (!columnListData) return;은undefined를 반환합니다. React 컴포넌트는 렌더링하지 않을 때 명시적으로null을 반환해야 합니다.다음과 같이 수정하세요:
if (!columnListData) { - return; + return null; }
🧹 Nitpick comments (1)
src/components/dashboard-detail/modal/ChangeCardModal.tsx (1)
27-34: 컴포넌트 문서화 추가 권장코딩 가이드라인에 따라 컴포넌트 단위로 TSDoc을 작성하여 props와 사용법을 설명하는 것이 좋습니다.
다음과 같은 TSDoc 추가를 고려해보세요:
+/** + * 카드(할 일) 편집 모달 컴포넌트 + * + * @example + * <ChangeCardModal + * memberData={memberData} + * modalName="editCard" + * initialValue={cardData} + * columnListData={columns} + * serverErrorMessage={null} + * onSubmit={handleCardUpdate} + * /> + */ export default function ChangeCardModal({ memberData, modalName,Based on coding guidelines requiring TSDoc for components.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/components/dashboard-detail/modal/ChangeCardModal.tsx(1 hunks)src/utils/card/updateCardRequestBody.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/utils/card/updateCardRequestBody.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.tsx
⚙️ CodeRabbit configuration file
**/*.tsx: reviews:
instructions: |
우리 팀은 다음의 코드 스타일 가이드를 따릅니다:[기본 규칙] - 변수는 const 우선 사용 - 파일명은 PascalCase로 작성 - 컴포넌트는 default export를 사용 - 파일명과 컴포넌트명은 반드시 일치 (PascalCase) - 불필요한 props는 전달하지 않기 - UI 관련 상태는 명확한 이름 사용 (isOpen, isLoading 등) [함수 네이밍 규칙] - 모든 함수는 **동사 + 목적어** - UI 이벤트 핸들러는 handle* 사용 (handleClick, handleToggle 등) - 내부 로직은 calculate / convert / format 등 의미 기반 네이밍 사용 [디렉토리 & 파일 구조] - 디렉토리는 kebab-case - 컴포넌트 파일은 PascalCase - assets는 index 기반 export (Icons, Images, Logos 등) - import 시 그룹 단위 import 사용 [상수 / 환경 변수] - SNAKE_CASE 사용 - 예: PAGE_SIZE, API_URL [함수 선언 규칙] - 컴포넌트는 선언식 function Component() 형태 - 컴포넌트 내부의 로직 함수는 화살표 함수 사용 - 유틸성 컴포넌트가 아니면 default export [주석 & 문서화] - TODO 주석 사용 (미완성 코드 표시) - TSDoc을 컴포넌트 단위로 활용 - 예시를 포함한 간단한 설명 추가
Files:
src/components/dashboard-detail/modal/ChangeCardModal.tsx
🧠 Learnings (1)
📚 Learning: 2025-11-29T18:56:30.435Z
Learnt from: aahreum
Repo: sprint-19-part3-7team/Taskify-Frontend PR: 265
File: src/pages/DashboardDetail.tsx:106-116
Timestamp: 2025-11-29T18:56:30.435Z
Learning: In src/pages/DashboardDetail.tsx, column names (title field) are allowed to contain leading or trailing whitespace. Trimming whitespace from column names is not required.
Applied to files:
src/components/dashboard-detail/modal/ChangeCardModal.tsx
🧬 Code graph analysis (1)
src/components/dashboard-detail/modal/ChangeCardModal.tsx (5)
src/types/members.ts (1)
MembersResponse(12-15)src/types/card.ts (2)
CardEditFormValue(39-47)Assignee(3-8)src/types/column.ts (1)
ColumnsResponse(10-13)src/components/dashboard/combobox/Combobox.tsx (2)
StatusComboboxValue(15-18)Combobox(46-80)src/constants/requestCardData.ts (2)
DUE_DATE(2-2)IMAGE_URL(3-4)
🔇 Additional comments (6)
src/components/dashboard-detail/modal/ChangeCardModal.tsx (6)
45-47: LGTM!단순 문자열 필드 업데이트를 위한 핸들러로, 깔끔하게 구현되었습니다.
49-70: LGTM!Combobox 변경사항을 처리하는 로직이 타입 좁히기(type narrowing)를 통해 안전하게 구현되었습니다. 이전 리뷰에서 지적된 타입 호환성 문제를 적절하게 해결했습니다.
72-86: LGTM!변경 감지 로직이 모든 편집 가능한 필드를 포괄적으로 검사하고 있으며, 필수 필드(제목, 설명)의 유효성 검증도 적절합니다.
88-95: LGTM!비동기 제출 처리와 에러 핸들링이 적절하게 구현되었습니다.
177-178: LGTM!DUE_DATE 상수를 사용한 기본값 처리 로직이 적절합니다. 기본값일 때 UI에 빈 문자열을 표시하여 사용자 경험을 개선했습니다.
209-213: LGTM!서버 에러와 로컬 에러를 우선순위에 따라 표시하는 로직이 명확하게 구현되었습니다.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
src/components/dashboard-detail/modal/ChangeCardModal.tsx (2)
52-73: 타입 단언(type assertion) 사용이전 리뷰에서 언급된 대로,
handleComboboxChange함수에서 타입 단언(as StatusComboboxValue | null,as UserComboboxValue | null)이 사용되고 있습니다. 기능적으로는 작동하지만, 타입 안전성을 위해 향후 리팩토링을 고려해 주세요.
144-152: Member 타입과 UserComboboxValue 타입 불일치이전 리뷰에서 언급된 대로,
Combobox.Item의valueprop에Member객체를 직접 전달하고 있습니다. 타입 호환성을 위해 필요한 속성만 추출하여 전달하는 것이 권장됩니다.- <Combobox.Item key={m.id} value={m}> + <Combobox.Item key={m.id} value={{ id: m.id, userId: m.userId, nickname: m.nickname, profileImageUrl: m.profileImageUrl }}>
🧹 Nitpick comments (2)
src/components/dashboard/combobox/ComboboxItem.tsx (1)
16-16:UserComboboxValue의userId필드가 비교에서 누락됨현재 로직은 이전 리뷰에서 제안한selectedValue?.id !== undefined검증을 추가했지만,UserComboboxValue의 경우id가 없고userId만 있는 경우를 처리하지 못합니다.
ComboboxTrigger.tsx(Line 64)에서는selectedValue.userId ?? selectedValue.id로 양쪽 필드를 모두 고려하는데, 이 비교 로직은id만 확인합니다. 타입 일관성을 위해 고려해 주세요:- const isSelected = selectedValue?.id !== undefined && selectedValue.id === value.id; + const isSelected = (() => { + if (!selectedValue) return false; + if ('nickname' in selectedValue && 'nickname' in value) { + const selId = selectedValue.userId ?? selectedValue.id; + const valId = (value as typeof selectedValue).userId ?? value.id; + return selId !== undefined && selId === valId; + } + return selectedValue.id !== undefined && selectedValue.id === value.id; + })();또는
UserComboboxValue와StatusComboboxValue에 discriminated union 패턴(kind같은 공통 리터럴 프로퍼티)을 도입하면 타입 가드가 더 안전해집니다.src/components/dashboard-detail/modal/ChangeCardModal.tsx (1)
91-98: 제출 전 에러 상태 초기화 고려
handleSubmit호출 시 이전 에러 메시지가 남아있을 수 있습니다. 제출 시작 시 에러 상태를 초기화하면 UX가 개선됩니다.const handleSubmit = async () => { + setErrorMessage(''); try { await onSubmit(formValue, imageFile); } catch (err) { const errorMsg = err instanceof Error ? err.message : '카드 수정 중 오류가 발생했습니다.'; setErrorMessage(errorMsg); } };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/components/dashboard-detail/modal/ChangeCardModal.tsx(1 hunks)src/components/dashboard/combobox/ComboboxItem.tsx(1 hunks)src/components/dashboard/combobox/ComboboxTrigger.tsx(4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.tsx
⚙️ CodeRabbit configuration file
**/*.tsx: reviews:
instructions: |
우리 팀은 다음의 코드 스타일 가이드를 따릅니다:[기본 규칙] - 변수는 const 우선 사용 - 파일명은 PascalCase로 작성 - 컴포넌트는 default export를 사용 - 파일명과 컴포넌트명은 반드시 일치 (PascalCase) - 불필요한 props는 전달하지 않기 - UI 관련 상태는 명확한 이름 사용 (isOpen, isLoading 등) [함수 네이밍 규칙] - 모든 함수는 **동사 + 목적어** - UI 이벤트 핸들러는 handle* 사용 (handleClick, handleToggle 등) - 내부 로직은 calculate / convert / format 등 의미 기반 네이밍 사용 [디렉토리 & 파일 구조] - 디렉토리는 kebab-case - 컴포넌트 파일은 PascalCase - assets는 index 기반 export (Icons, Images, Logos 등) - import 시 그룹 단위 import 사용 [상수 / 환경 변수] - SNAKE_CASE 사용 - 예: PAGE_SIZE, API_URL [함수 선언 규칙] - 컴포넌트는 선언식 function Component() 형태 - 컴포넌트 내부의 로직 함수는 화살표 함수 사용 - 유틸성 컴포넌트가 아니면 default export [주석 & 문서화] - TODO 주석 사용 (미완성 코드 표시) - TSDoc을 컴포넌트 단위로 활용 - 예시를 포함한 간단한 설명 추가
Files:
src/components/dashboard-detail/modal/ChangeCardModal.tsxsrc/components/dashboard/combobox/ComboboxTrigger.tsxsrc/components/dashboard/combobox/ComboboxItem.tsx
🧠 Learnings (2)
📚 Learning: 2025-11-29T18:56:30.435Z
Learnt from: aahreum
Repo: sprint-19-part3-7team/Taskify-Frontend PR: 265
File: src/pages/DashboardDetail.tsx:106-116
Timestamp: 2025-11-29T18:56:30.435Z
Learning: In src/pages/DashboardDetail.tsx, column names (title field) are allowed to contain leading or trailing whitespace. Trimming whitespace from column names is not required.
Applied to files:
src/components/dashboard-detail/modal/ChangeCardModal.tsx
📚 Learning: 2025-11-22T16:39:21.177Z
Learnt from: aahreum
Repo: sprint-19-part3-7team/Taskify-Frontend PR: 93
File: src/components/dashboard/dropdown/DropdownMenuSearchTrigger.tsx:32-63
Timestamp: 2025-11-22T16:39:21.177Z
Learning: In the DropdownMenuSearchTrigger component (src/components/dashboard/dropdown/DropdownMenuSearchTrigger.tsx), a div element is intentionally used instead of a button because the component conditionally renders an input element when open, and nesting input inside button is invalid HTML.
Applied to files:
src/components/dashboard/combobox/ComboboxTrigger.tsx
🧬 Code graph analysis (2)
src/components/dashboard/combobox/ComboboxTrigger.tsx (2)
src/components/common/avatar/Avatar.tsx (1)
Avatar(25-44)src/components/dashboard-detail/card/CardStatusBadge.tsx (1)
CardStatusBadge(8-19)
src/components/dashboard/combobox/ComboboxItem.tsx (1)
src/components/dashboard/combobox/Combobox.tsx (2)
UserComboboxValue(8-13)StatusComboboxValue(15-18)
🔇 Additional comments (7)
src/components/dashboard/combobox/ComboboxTrigger.tsx (2)
54-82:valueContent조건부 렌더링 로직 검토전반적인 구조는 잘 구성되어 있습니다. 다만 몇 가지 검토가 필요합니다:
Line 64:
UserComboboxValue에서id와userId모두 optional입니다.selectedValue.userId ?? selectedValue.id가undefined를 반환할 수 있어 Avatar 컴포넌트에 전달되는user.id가undefined가 될 수 있습니다.Line 80-81: fallback 케이스에서
String(selectedValue)는 예상치 못한 객체 타입에 대해"[object Object]"를 출력할 수 있습니다. 이 케이스가 실제로 발생할 수 있는지, 아니면 타입 가드로 방어할 수 있는지 확인이 필요합니다.} else if ('nickname' in selectedValue) { valueContent = ( <div className='flex items-center gap-2'> <Avatar size='s' user={{ - id: selectedValue.userId ?? selectedValue.id, + id: selectedValue.userId ?? selectedValue.id ?? 0, nickname: selectedValue.nickname, profileImageUrl: selectedValue.profileImageUrl, }}>
105-124: 닫힌 상태의 렌더링 구조 LGTM
isOpen이false일 때valueContent를 표시하고, 스크린 리더용 숨겨진 input을 유지하는 구조가 적절합니다.sr-only클래스와 포커스 핸들링도 접근성 측면에서 잘 구현되어 있습니다.src/components/dashboard/combobox/ComboboxItem.tsx (1)
18-26:handleListClick토글 로직 LGTM선택/해제 토글 후
setIsOpen(false)와setSearchQuery('')호출로 상태를 깔끔하게 정리합니다. 코딩 가이드라인의handle*네이밍 컨벤션도 준수했습니다.src/components/dashboard-detail/modal/ChangeCardModal.tsx (4)
75-89: LGTM!
isDisabled메모이제이션이 모든 필드 변경 감지와 유효성 검사를 포괄적으로 처리하고 있습니다. 태그 비교에JSON.stringify를 사용하는 것은 작은 배열에서는 적절합니다.
100-102: LGTM!이전 리뷰에서 지적된 암시적
undefined반환 문제가null반환으로 올바르게 수정되었습니다.
219-226: LGTM!모달 푸터 버튼 구현이 적절합니다.
isDisabled상태와 연동된 제출 버튼 비활성화 및 취소 버튼의 모달 닫기 처리가 올바르게 구현되었습니다.
202-208: The current code correctly handles thenullcase fordefaultImageUrl. TheImageUploadcomponent is explicitly typed to acceptstring | nulland properly handles null values using a truthy check on line 53 (if (defaultImageUrl)). PassingnullwhendefaultImageUrlis neitherIMAGE_URLnor a valid string is the intended behavior, as the component will render without a preview image. No modification is needed.Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (3)
src/components/dashboard-detail/modal/ChangeCardModal.tsx (2)
140-148:Member객체를Combobox.Item에 직접 전달 시 타입 불일치 가능성
Combobox.Item의valueprop은UserComboboxValue | StatusComboboxValue를 기대합니다.Member타입이UserComboboxValue와 완전히 호환되는지 확인이 필요합니다.Member에 추가 속성이 있다면 명시적으로 필요한 속성만 전달하세요.<Combobox.Item key={m.id} value={{ id: m.id, userId: m.userId, nickname: m.nickname, profileImageUrl: m.profileImageUrl }}>#!/bin/bash # Member와 UserComboboxValue 타입 비교 echo "=== Member type ===" rg -nA 10 'export interface Member\b' src/types/members.ts echo -e "\n=== UserComboboxValue type ===" rg -nA 10 'export interface UserComboboxValue' src/components/dashboard/combobox/Combobox.tsx
39-42:initialValue변경 시formValue와defaultImageUrl이 동기화되지 않음모달이 다른 카드로 재사용될 때
initialValue가 변경되어도formValue와defaultImageUrl이 갱신되지 않습니다. PR 코멘트에서 언급된 "기존 값이 한 번 이중 렌더링 후 모달 닫힘" 문제와 관련될 수 있습니다.+ import { useEffect, useMemo, useState } from 'react'; - import { useMemo, useState } from 'react'; const [formValue, setFormValue] = useState(initialValue); const [imageFile, setImageFile] = useState<File | null>(null); const [defaultImageUrl, setDefaultImageUrl] = useState<string | null>(initialValue.imageUrl); const [errorMessage, setErrorMessage] = useState(''); + useEffect(() => { + setFormValue(initialValue); + setDefaultImageUrl(initialValue.imageUrl); + setImageFile(null); + setErrorMessage(''); + }, [initialValue]);src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx (1)
112-126:updateCardRequestBody함수의 4번째 인자가 잘못되었습니다
updateCardRequestBody의 4번째 매개변수는currentUserId로, 담당자(assignee) 폴백에 사용됩니다. 현재formValue.columnId(컬럼 ID)를 전달하고 있어 담당자 할당 로직이 잘못될 수 있습니다.
src/utils/card/updateCardRequestBody.ts시그니처:updateCardRequestBody(formValue, cardDetailData, imageUrl, currentUserId?: number) // assigneeUserId = formValue.assigneeUser?.userId ?? cardDetailData?.assignee.id ?? currentUserId ?? 0const handleSubmitUpdateCard = async ( formValue: CardEditFormValue, imageFile: File | null ): Promise<void> => { const uploadedImageUrl = await uploadCardImage(formValue.columnId, imageFile); const body = updateCardRequestBody( formValue, cardDetailQuery.data, uploadedImageUrl, - formValue.columnId + undefined // 또는 현재 로그인한 사용자 ID를 전달 ); await updateCardMutation.mutate({ id: cardId, body }); };
formValue.assigneeUser가 이미 선택된 담당자를 포함하고 있으므로, 폴백으로 현재 사용자 ID가 필요하다면 auth context에서 가져오세요. 폴백이 불필요하다면undefined를 전달하세요.
🧹 Nitpick comments (2)
src/components/dashboard-detail/modal/ChangeCardModal.tsx (1)
48-69:handleComboboxChange타입 단언 사용 최소화 권장현재
as StatusComboboxValue | null와as UserComboboxValue | null타입 단언이 사용되고 있습니다. 런타임에서 타입 안전성을 보장하려면 타입 가드를 사용하는 것이 좋습니다.const handleComboboxChange = ( key: 'columnId' | 'assigneeUser', value: StatusComboboxValue | UserComboboxValue | null ) => { setFormValue((prev) => { if (key === 'columnId') { - const newValue = value as StatusComboboxValue | null; + const newValue = value && 'title' in value ? value : null; return { ...prev, columnId: newValue?.id ?? prev.columnId, }; } if (key === 'assigneeUser') { - const newValue = value as UserComboboxValue | null; + const newValue = value && 'userId' in value ? value : null; return { ...prev, assigneeUser: newValue, }; } return prev; }); };src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx (1)
102-105: 모달 전환 시handleModalCloseAll사용 확인
handleCardEdit에서detailModal.handleModalCloseAll()을 호출하면 URL 검색 파라미터의 모든 모달이 닫힙니다. 다른 모달이 동시에 열려 있을 경우 의도치 않게 닫힐 수 있습니다.detailModal.handleModalClose()로 현재 모달만 닫는 것이 더 안전할 수 있습니다.const handleCardEdit = () => { - detailModal.handleModalCloseAll(); + detailModal.handleModalClose(); editModal.handleModalOpen(); };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/components/dashboard-detail/modal/ChangeCardModal.tsx(1 hunks)src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx(4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.tsx
⚙️ CodeRabbit configuration file
**/*.tsx: reviews:
instructions: |
우리 팀은 다음의 코드 스타일 가이드를 따릅니다:[기본 규칙] - 변수는 const 우선 사용 - 파일명은 PascalCase로 작성 - 컴포넌트는 default export를 사용 - 파일명과 컴포넌트명은 반드시 일치 (PascalCase) - 불필요한 props는 전달하지 않기 - UI 관련 상태는 명확한 이름 사용 (isOpen, isLoading 등) [함수 네이밍 규칙] - 모든 함수는 **동사 + 목적어** - UI 이벤트 핸들러는 handle* 사용 (handleClick, handleToggle 등) - 내부 로직은 calculate / convert / format 등 의미 기반 네이밍 사용 [디렉토리 & 파일 구조] - 디렉토리는 kebab-case - 컴포넌트 파일은 PascalCase - assets는 index 기반 export (Icons, Images, Logos 등) - import 시 그룹 단위 import 사용 [상수 / 환경 변수] - SNAKE_CASE 사용 - 예: PAGE_SIZE, API_URL [함수 선언 규칙] - 컴포넌트는 선언식 function Component() 형태 - 컴포넌트 내부의 로직 함수는 화살표 함수 사용 - 유틸성 컴포넌트가 아니면 default export [주석 & 문서화] - TODO 주석 사용 (미완성 코드 표시) - TSDoc을 컴포넌트 단위로 활용 - 예시를 포함한 간단한 설명 추가
Files:
src/components/dashboard-detail/modal/ChangeCardModal.tsxsrc/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx
🧠 Learnings (4)
📚 Learning: 2025-11-29T18:56:30.435Z
Learnt from: aahreum
Repo: sprint-19-part3-7team/Taskify-Frontend PR: 265
File: src/pages/DashboardDetail.tsx:106-116
Timestamp: 2025-11-29T18:56:30.435Z
Learning: In src/pages/DashboardDetail.tsx, column names (title field) are allowed to contain leading or trailing whitespace. Trimming whitespace from column names is not required.
Applied to files:
src/components/dashboard-detail/modal/ChangeCardModal.tsx
📚 Learning: 2025-11-30T15:48:53.701Z
Learnt from: aahreum
Repo: sprint-19-part3-7team/Taskify-Frontend PR: 283
File: src/utils/card/createCardReqBody.ts:20-30
Timestamp: 2025-11-30T15:48:53.701Z
Learning: In src/utils/card/createCardReqBody.ts, the backend requires a default IMAGE_URL fallback (`imageUrl ?? IMAGE_URL`) when no image is provided, rather than accepting null. This is part of the server API contract, similar to how it requires assigneeUserId: 0 and tags: [''] as default values.
Applied to files:
src/components/dashboard-detail/modal/ChangeCardModal.tsxsrc/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx
📚 Learning: 2025-11-30T15:48:27.926Z
Learnt from: aahreum
Repo: sprint-19-part3-7team/Taskify-Frontend PR: 283
File: src/utils/card/createCardReqBody.ts:17-18
Timestamp: 2025-11-30T15:48:27.926Z
Learning: In src/utils/card/createCardReqBody.ts, the backend accepts `assigneeUserId: 0` as a valid value when no assignee is selected and no currentUserId is provided. This is part of the server API contract.
Applied to files:
src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx
📚 Learning: 2025-12-01T01:09:10.680Z
Learnt from: aahreum
Repo: sprint-19-part3-7team/Taskify-Frontend PR: 287
File: src/lib/apis/Invitations.ts:31-35
Timestamp: 2025-12-01T01:09:10.680Z
Learning: In this codebase, mutation functions (POST/PUT/DELETE) return the full response object (`res`) instead of `res.data`, because `useMutation` internally processes `res.data`. Only GET methods return `res.data` directly.
Applied to files:
src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx
🧬 Code graph analysis (2)
src/components/dashboard-detail/modal/ChangeCardModal.tsx (4)
src/types/members.ts (1)
MembersResponse(12-15)src/types/card.ts (1)
CardEditFormValue(39-47)src/types/column.ts (1)
ColumnsResponse(10-13)src/constants/requestCardData.ts (2)
DUE_DATE(2-2)IMAGE_URL(3-4)
src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx (9)
src/types/card.ts (2)
CardDetailResponse(10-22)CardEditFormValue(39-47)src/types/members.ts (1)
MembersResponse(12-15)src/types/column.ts (1)
ColumnsResponse(10-13)src/hooks/useModal.ts (1)
useModal(4-50)src/lib/apis/cards.ts (3)
deleteCard(63-66)UpdateCardType(3-11)updateCard(51-54)src/utils/card/uploadCardImage.ts (1)
uploadCardImage(4-21)src/utils/card/updateCardRequestBody.ts (1)
updateCardRequestBody(6-28)src/components/common/modal/ModalPortal.tsx (1)
ModalPortal(3-6)src/components/dashboard-detail/modal/ChangeCardModal.tsx (1)
ChangeCardModal(30-226)
🔇 Additional comments (8)
src/components/dashboard-detail/modal/ChangeCardModal.tsx (5)
71-85:isDisabled로직 구현 적절함변경 감지와 필수 필드 유효성 검사가 잘 구현되어 있습니다.
useMemo를 사용하여 불필요한 재계산을 방지하고,JSON.stringify로 태그 배열 비교도 적절합니다.
87-94: 에러 핸들링 구현 적절함
try-catch로 에러를 캡처하고 사용자에게 표시하는 방식이 적절합니다.
96-98:null반환으로 수정됨 - 확인 완료이전 리뷰에서 지적된
return;(undefined 반환) 문제가return null;로 수정되었습니다.
111-127: 컬럼 Combobox 구현 개선됨이전 리뷰에서 지적된
find()의undefined반환 문제가?? null로 해결되었고,map()으로 필요한 속성만 추출하여StatusComboboxValue타입에 맞게 전달하고 있습니다.
175-182: 마감일 입력 처리 확인 필요
DUE_DATE상수('1000-10-10 10:00')와 비교하여 빈 문자열로 표시하는 로직이 있습니다. 사용자가 마감일을 지우고 저장할 때DUE_DATE가 다시 설정되는지, 또는 실제 null/빈 값으로 저장되는지 확인이 필요합니다.src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx (3)
70-85: mutation 구현 적절함
deleteCardMutation과updateCardMutation이 잘 구현되어 있습니다.onSuccess콜백에서 부모 컴포넌트 알림, 데이터 리페치, 모달 닫기가 적절히 처리됩니다.
166-183: ChangeCardModal 렌더링 및 initialValue 구성 적절함
cardDetailQuery.data에서 필요한 필드를 추출하여initialValue를 구성하고 있습니다.serverErrorMessage로 mutation 에러도 적절히 전달됩니다.
128-164: 상세 모달 렌더링 로직 검토
detailModal.isOpen조건으로 렌더링을 제어하고 있습니다. PR 코멘트에서 언급된 "기존 값 이중 렌더링" 문제가 모달 전환 타이밍과 관련될 수 있습니다.handleCardEdit에서handleModalCloseAll후 바로editModal.handleModalOpen을 호출하므로, 비동기 상태 업데이트로 인해 일시적인 깜빡임이 발생할 수 있습니다.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx (2)
60-63: 카드 편집 진입 시handleModalCloseAll사용으로 다른 모달까지 함께 닫힐 수 있습니다
useModal구현을 보면handleModalCloseAll은 URL query 전체를 비우기 때문에,handleCardEdit에서 이를 호출하면 동일한 URL 기반으로 열린 다른 모달들도 전부 닫히는 부작용이 있을 수 있습니다.const handleCardEdit = () => { detailModal.handleModalCloseAll(); editModal.handleModalOpen(); };단순히 카드 상세 모달만 닫으면 되므로, 아래처럼
handleModalClose만 사용하는 쪽이 의도에 더 가깝고, 다른 모달과의 독립성도 보장됩니다.- const handleCardEdit = () => { - detailModal.handleModalCloseAll(); - editModal.handleModalOpen(); - }; + const handleCardEdit = () => { + detailModal.handleModalClose(); + editModal.handleModalOpen(); + };이렇게 바꾸면, 동일한 URL 쿼리 기반 모달 시스템을 쓰더라도 다른 모달 상태에 영향을 주지 않고 디테일 → 수정 모달로 자연스럽게 전환됩니다.
Also applies to: 104-107
168-181: 편집 모달 초기 column 값은cardDetailQuery.data.columnId를 사용하는 편이 더 일관됩니다현재
ChangeCardModal의initialValue.columnId는 부모에서 받은columnIdprop을 그대로 사용하고 있습니다.initialValue={{ columnId, assigneeUser: cardDetailQuery.data.assignee, // ... }}반면 나머지 필드들은 모두
cardDetailQuery.data를 사용하고 있고, 실제 최신 카드 상태의 단일 소스 오브 트루스도cardDetailQuery.data이기 때문에:- initialValue={{ - columnId, + initialValue={{ + columnId: cardDetailQuery.data.columnId, assigneeUser: cardDetailQuery.data.assignee, title: cardDetailQuery.data.title, description: cardDetailQuery.data.description, dueDate: cardDetailQuery.data.dueDate, tags: cardDetailQuery.data.tags, imageUrl: cardDetailQuery.data.imageUrl, }}처럼 맞춰두면,
- 상위에서 columnId 상태 업데이트가 한 템포 늦더라도
- 편집 모달을 다시 열었을 때 항상 서버 기준 최신 컬럼 값이 반영
되어, “컬럼 값이 안 바뀜”처럼 보이는 상황을 줄일 수 있습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx(4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.tsx
⚙️ CodeRabbit configuration file
**/*.tsx: reviews:
instructions: |
우리 팀은 다음의 코드 스타일 가이드를 따릅니다:[기본 규칙] - 변수는 const 우선 사용 - 파일명은 PascalCase로 작성 - 컴포넌트는 default export를 사용 - 파일명과 컴포넌트명은 반드시 일치 (PascalCase) - 불필요한 props는 전달하지 않기 - UI 관련 상태는 명확한 이름 사용 (isOpen, isLoading 등) [함수 네이밍 규칙] - 모든 함수는 **동사 + 목적어** - UI 이벤트 핸들러는 handle* 사용 (handleClick, handleToggle 등) - 내부 로직은 calculate / convert / format 등 의미 기반 네이밍 사용 [디렉토리 & 파일 구조] - 디렉토리는 kebab-case - 컴포넌트 파일은 PascalCase - assets는 index 기반 export (Icons, Images, Logos 등) - import 시 그룹 단위 import 사용 [상수 / 환경 변수] - SNAKE_CASE 사용 - 예: PAGE_SIZE, API_URL [함수 선언 규칙] - 컴포넌트는 선언식 function Component() 형태 - 컴포넌트 내부의 로직 함수는 화살표 함수 사용 - 유틸성 컴포넌트가 아니면 default export [주석 & 문서화] - TODO 주석 사용 (미완성 코드 표시) - TSDoc을 컴포넌트 단위로 활용 - 예시를 포함한 간단한 설명 추가
Files:
src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx
🧠 Learnings (3)
📚 Learning: 2025-11-30T15:48:27.926Z
Learnt from: aahreum
Repo: sprint-19-part3-7team/Taskify-Frontend PR: 283
File: src/utils/card/createCardReqBody.ts:17-18
Timestamp: 2025-11-30T15:48:27.926Z
Learning: In src/utils/card/createCardReqBody.ts, the backend accepts `assigneeUserId: 0` as a valid value when no assignee is selected and no currentUserId is provided. This is part of the server API contract.
Applied to files:
src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx
📚 Learning: 2025-11-30T15:48:53.701Z
Learnt from: aahreum
Repo: sprint-19-part3-7team/Taskify-Frontend PR: 283
File: src/utils/card/createCardReqBody.ts:20-30
Timestamp: 2025-11-30T15:48:53.701Z
Learning: In src/utils/card/createCardReqBody.ts, the backend requires a default IMAGE_URL fallback (`imageUrl ?? IMAGE_URL`) when no image is provided, rather than accepting null. This is part of the server API contract, similar to how it requires assigneeUserId: 0 and tags: [''] as default values.
Applied to files:
src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx
📚 Learning: 2025-12-01T01:09:10.680Z
Learnt from: aahreum
Repo: sprint-19-part3-7team/Taskify-Frontend PR: 287
File: src/lib/apis/Invitations.ts:31-35
Timestamp: 2025-12-01T01:09:10.680Z
Learning: In this codebase, mutation functions (POST/PUT/DELETE) return the full response object (`res`) instead of `res.data`, because `useMutation` internally processes `res.data`. Only GET methods return `res.data` directly.
Applied to files:
src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx
🧬 Code graph analysis (1)
src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx (7)
src/types/card.ts (2)
CardDetailResponse(10-22)CardEditFormValue(39-47)src/types/members.ts (1)
MembersResponse(12-15)src/types/column.ts (1)
ColumnsResponse(10-13)src/hooks/useModal.ts (1)
useModal(4-50)src/lib/apis/cards.ts (3)
deleteCard(63-66)UpdateCardType(3-11)updateCard(51-54)src/utils/card/uploadCardImage.ts (1)
uploadCardImage(4-21)src/utils/card/updateCardRequestBody.ts (1)
updateCardRequestBody(6-28)
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx(4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.tsx
⚙️ CodeRabbit configuration file
**/*.tsx: reviews:
instructions: |
우리 팀은 다음의 코드 스타일 가이드를 따릅니다:[기본 규칙] - 변수는 const 우선 사용 - 파일명은 PascalCase로 작성 - 컴포넌트는 default export를 사용 - 파일명과 컴포넌트명은 반드시 일치 (PascalCase) - 불필요한 props는 전달하지 않기 - UI 관련 상태는 명확한 이름 사용 (isOpen, isLoading 등) [함수 네이밍 규칙] - 모든 함수는 **동사 + 목적어** - UI 이벤트 핸들러는 handle* 사용 (handleClick, handleToggle 등) - 내부 로직은 calculate / convert / format 등 의미 기반 네이밍 사용 [디렉토리 & 파일 구조] - 디렉토리는 kebab-case - 컴포넌트 파일은 PascalCase - assets는 index 기반 export (Icons, Images, Logos 등) - import 시 그룹 단위 import 사용 [상수 / 환경 변수] - SNAKE_CASE 사용 - 예: PAGE_SIZE, API_URL [함수 선언 규칙] - 컴포넌트는 선언식 function Component() 형태 - 컴포넌트 내부의 로직 함수는 화살표 함수 사용 - 유틸성 컴포넌트가 아니면 default export [주석 & 문서화] - TODO 주석 사용 (미완성 코드 표시) - TSDoc을 컴포넌트 단위로 활용 - 예시를 포함한 간단한 설명 추가
Files:
src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx
🧠 Learnings (2)
📚 Learning: 2025-11-30T15:48:53.701Z
Learnt from: aahreum
Repo: sprint-19-part3-7team/Taskify-Frontend PR: 283
File: src/utils/card/createCardReqBody.ts:20-30
Timestamp: 2025-11-30T15:48:53.701Z
Learning: In src/utils/card/createCardReqBody.ts, the backend requires a default IMAGE_URL fallback (`imageUrl ?? IMAGE_URL`) when no image is provided, rather than accepting null. This is part of the server API contract, similar to how it requires assigneeUserId: 0 and tags: [''] as default values.
Applied to files:
src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx
📚 Learning: 2025-12-01T01:09:10.680Z
Learnt from: aahreum
Repo: sprint-19-part3-7team/Taskify-Frontend PR: 287
File: src/lib/apis/Invitations.ts:31-35
Timestamp: 2025-12-01T01:09:10.680Z
Learning: In this codebase, mutation functions (POST/PUT/DELETE) return the full response object (`res`) instead of `res.data`, because `useMutation` internally processes `res.data`. Only GET methods return `res.data` directly.
Applied to files:
src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx
🔇 Additional comments (6)
src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx (6)
1-20: 새로운 편집 기능을 위한 import가 적절하게 추가되었습니다ChangeCardModal, useModal, useUserContext, 관련 타입 및 유틸리티 함수들이 모두 적절하게 import되어 있습니다.
36-45: 인터페이스 확장이 적절합니다편집 흐름을 지원하기 위해
onUpdateCard,memberData,columnListDataprops가 명확한 타입과 함께 추가되었습니다.
57-62: 듀얼 모달 시스템이 잘 구성되었습니다
detailModal과editModal을 분리하여 관리하는 방식이 명확하며, 반응형 처리와 사용자 컨텍스트 접근도 적절합니다.
72-87: Mutation 설정이 올바릅니다
deleteCardMutation과updateCardMutation모두 적절한 타입과 콜백으로 구성되어 있으며, 성공 시 상태 업데이트 및 refetch 로직이 정확합니다.
132-189: 렌더링 로직이 잘 구성되어 있습니다듀얼 모달 시스템의 조건부 렌더링이 명확하며, props 전달과 초기값 설정도 적절합니다.
ChangeCardModal에columnListData={columnListData}(line 174)를 전달할 때 null 가능성이 있으므로,ChangeCardModal내부에서 null 처리가 되어있는지 확인하세요.
77-87: The image update flow is correctly implemented through the established callback mechanism. When a card is updated:
onUpdateCard(updated)immediately applies the updated card data (including newimageUrl) to the card list viahandleUpdateCardin ColumnCardList (lines 111-121), which updates theinfiniteDatastatecardDetailQuery.refetch()syncs the detail view without needing to be awaited- The modal closes after initiating these updates
The
DashboardCardcomponent renders the image fromcardData.imageUrl(line 46), which is already updated via the callback before the modal closes. The current implementation already provides optimistic updates through theonUpdateCardcallback pattern.No changes are necessary—the code correctly handles image reflection through its established state management pattern.
Likely an incorrect or invalid review comment.
src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx (1)
114-130: 이미지 fallback 체인에formValue.imageUrl을 추가하세요.Line 120의 fallback 체인에
formValue.imageUrl이 누락되어 있습니다. 이전 리뷰에서 제안된 대로 이를 추가하면 이미지 변경 사항이 즉시 반영되지 않는 문제(PR 목표에 명시됨)를 해결하는 데 도움이 될 수 있습니다.다음과 같이 수정하세요:
const handleSubmitUpdateCard = async ( formValue: CardEditFormValue, imageFile: File | null ): Promise<void> => { const uploadedImageUrl = await uploadCardImage(formValue.columnId, imageFile); - const nextImageUrl = uploadedImageUrl ?? cardDetailQuery.data?.imageUrl ?? null; + const nextImageUrl = + uploadedImageUrl ?? + formValue.imageUrl ?? + cardDetailQuery.data?.imageUrl ?? + null; const body = updateCardRequestBody( formValue, cardDetailQuery.data, nextImageUrl, userProfile?.id ); await updateCardMutation.mutate({ id: cardId, body }); };이렇게 하면 폼에 이미 있던 이미지 URL도 fallback 체인에 포함되어 이미지 업데이트가 더 안정적으로 처리됩니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx(4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.tsx
⚙️ CodeRabbit configuration file
**/*.tsx: reviews:
instructions: |
우리 팀은 다음의 코드 스타일 가이드를 따릅니다:[기본 규칙] - 변수는 const 우선 사용 - 파일명은 PascalCase로 작성 - 컴포넌트는 default export를 사용 - 파일명과 컴포넌트명은 반드시 일치 (PascalCase) - 불필요한 props는 전달하지 않기 - UI 관련 상태는 명확한 이름 사용 (isOpen, isLoading 등) [함수 네이밍 규칙] - 모든 함수는 **동사 + 목적어** - UI 이벤트 핸들러는 handle* 사용 (handleClick, handleToggle 등) - 내부 로직은 calculate / convert / format 등 의미 기반 네이밍 사용 [디렉토리 & 파일 구조] - 디렉토리는 kebab-case - 컴포넌트 파일은 PascalCase - assets는 index 기반 export (Icons, Images, Logos 등) - import 시 그룹 단위 import 사용 [상수 / 환경 변수] - SNAKE_CASE 사용 - 예: PAGE_SIZE, API_URL [함수 선언 규칙] - 컴포넌트는 선언식 function Component() 형태 - 컴포넌트 내부의 로직 함수는 화살표 함수 사용 - 유틸성 컴포넌트가 아니면 default export [주석 & 문서화] - TODO 주석 사용 (미완성 코드 표시) - TSDoc을 컴포넌트 단위로 활용 - 예시를 포함한 간단한 설명 추가
Files:
src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx
🧠 Learnings (3)
📚 Learning: 2025-11-30T15:48:53.701Z
Learnt from: aahreum
Repo: sprint-19-part3-7team/Taskify-Frontend PR: 283
File: src/utils/card/createCardReqBody.ts:20-30
Timestamp: 2025-11-30T15:48:53.701Z
Learning: In src/utils/card/createCardReqBody.ts, the backend requires a default IMAGE_URL fallback (`imageUrl ?? IMAGE_URL`) when no image is provided, rather than accepting null. This is part of the server API contract, similar to how it requires assigneeUserId: 0 and tags: [''] as default values.
Applied to files:
src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx
📚 Learning: 2025-11-30T15:48:27.926Z
Learnt from: aahreum
Repo: sprint-19-part3-7team/Taskify-Frontend PR: 283
File: src/utils/card/createCardReqBody.ts:17-18
Timestamp: 2025-11-30T15:48:27.926Z
Learning: In src/utils/card/createCardReqBody.ts, the backend accepts `assigneeUserId: 0` as a valid value when no assignee is selected and no currentUserId is provided. This is part of the server API contract.
Applied to files:
src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx
📚 Learning: 2025-12-01T01:09:10.680Z
Learnt from: aahreum
Repo: sprint-19-part3-7team/Taskify-Frontend PR: 287
File: src/lib/apis/Invitations.ts:31-35
Timestamp: 2025-12-01T01:09:10.680Z
Learning: In this codebase, mutation functions (POST/PUT/DELETE) return the full response object (`res`) instead of `res.data`, because `useMutation` internally processes `res.data`. Only GET methods return `res.data` directly.
Applied to files:
src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx
🔇 Additional comments (1)
src/components/dashboard-detail/modal/card-detail-modal/CardDetailModal.tsx (1)
77-87: mutation 타입 정의와 에러 처리가 올바르게 구현되었습니다.
updateCardMutation의 타입 정의와onSuccess핸들러가 적절하게 구현되었습니다. 업데이트된 데이터를 부모에게 전달하고, 카드 상세 정보를 다시 가져오며, 편집 모달을 닫는 흐름이 명확합니다.
✅ PR 체크리스트
1. 코드 & 기능
2. UI
3. 컨벤션
🔗 이슈 번호
✨ 작업한 내용
💁 리뷰 요청 / 코멘트
💡 참고사항
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선 사항
버그 수정
✏️ Tip: You can customize this high-level summary in your review settings.