Conversation
|
Caution Review failedThe pull request is closed. """ Walkthrough티켓 옵션 생성, 수정, 드래그 앤 드롭 기능을 전면적으로 리팩터링하고 확장하는 대규모 변경입니다. 새로운 API 클라이언트, 커스텀 훅, 리듀서, 모델, UI 컴포넌트가 추가되었으며, 기존 페이지와 컴포넌트는 서버 기반 데이터와 중앙 집중식 폼 로직을 사용하도록 전환되었습니다. 라우트 및 라우터 설정도 이에 맞게 확장되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant TicketOptionPage
participant useTicketOptionDnD
participant API
User->>TicketOptionPage: 옵션을 드래그 앤 드롭
TicketOptionPage->>useTicketOptionDnD: onDragEnd 호출
useTicketOptionDnD->>API: attach/detachTicketOption API 호출
API-->>useTicketOptionDnD: 응답 반환
useTicketOptionDnD-->>TicketOptionPage: 상태 갱신 트리거
TicketOptionPage-->>User: UI 갱신
sequenceDiagram
participant User
participant TicketOptionCreatePage
participant useTicketOptionForm
participant useTicketOptionHook
participant API
User->>TicketOptionCreatePage: 폼 입력 및 저장 클릭
TicketOptionCreatePage->>useTicketOptionForm: handleSave 호출
useTicketOptionForm->>useTicketOptionHook: create/modifyTicketOptionMutation 실행
useTicketOptionHook->>API: create/modifyTicketOption API 호출
API-->>useTicketOptionHook: 응답 반환
useTicketOptionHook-->>useTicketOptionForm: 성공/실패 처리
useTicketOptionForm-->>TicketOptionCreatePage: 결과 반영
TicketOptionCreatePage-->>User: 네비게이션/알림 등 피드백
Possibly related PRs
Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (19)
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. 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
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 19
🧹 Nitpick comments (15)
src/main.tsx (1)
6-6: ReactQueryDevtools import: 개발용 의존성 분리 및 조건부 로딩 고려
ReactQueryDevtools는 개발 환경에서만 사용되는 도구이므로,package.json의devDependencies로 이동하고 프로덕션 번들에 포함되지 않도록 동적 import 혹은process.env.NODE_ENV === 'development'조건부 로딩을 적용하는 것을 권장합니다.프로덕션 빌드에 devtools가 포함되지 않았는지 확인해주세요.
src/app/routes/routes.ts (1)
45-45: 라우트 경로의 의미적 일관성 개선 권장편집 기능임에도 불구하고 경로에 'create'가 포함되어 있어 혼란을 줄 수 있습니다. 일반적으로 편집 기능은
/edit/:id패턴을 사용합니다.다음과 같이 변경하는 것을 고려해보세요:
- ticketOptionEdit: `${MAIN_ROUTES.dashboard}/ticket/option/create/:optionId`, + ticketOptionEdit: `${MAIN_ROUTES.dashboard}/ticket/option/edit/:optionId`,이렇게 하면 URL만 보고도 생성과 편집을 명확히 구분할 수 있습니다.
src/features/ticket/ui/TicketOptionListSection.tsx (2)
22-42: React key로 index 사용 시 주의사항옵션을 추가/삭제할 때 React의 재조정(reconciliation) 과정에서 문제가 발생할 수 있습니다. 특히 입력 필드의 포커스가 유지되지 않을 수 있습니다.
각 옵션에 고유 ID를 부여하는 것을 고려해보세요:
-{getActiveOptions().options.map((option, index) => ( - <React.Fragment key={index}> +{getActiveOptions().options.map((option, index) => ( + <React.Fragment key={`option-${state.question.responseFormat}-${index}`}>또는 옵션 데이터 구조에 고유 ID를 추가하는 것이 더 좋은 해결책일 수 있습니다.
55-55: 빈 div 렌더링에 대한 설명 추가 권장'자유로운 텍스트' 형식일 때 빈 div가 렌더링되는 것이 의도적인지 명확하지 않습니다.
주석을 추가하거나 안내 메시지를 표시하는 것을 고려해보세요:
-{state.question.responseFormat === '자유로운 텍스트' && <div></div>} +{state.question.responseFormat === '자유로운 텍스트' && ( + <div className="text-gray-500 text-sm"> + 자유로운 텍스트 응답은 별도의 옵션이 필요하지 않습니다. + </div> +)}src/features/ticket/ui/TicketOptionFormSection.tsx (1)
34-34: Nullish coalescing 연산자 주변 공백 일관성코드 스타일 일관성을 위해 공백을 추가하세요.
-value={state.question.description?? ''} +value={state.question.description ?? ''}src/features/ticket/hooks/useTicketOptionForm.ts (1)
218-218: 일관된 알림 시스템 사용 필요다른 곳에서는 커스텀 alert를 사용하는데 여기서는
window.alert를 사용합니다.일관성을 위해 프로젝트에서 사용하는 알림 시스템(토스트, 모달 등)을 사용하는 것을 권장합니다.
src/pages/dashboard/ui/ticket/TicketOptionPage.tsx (3)
80-80: React children 전달 방식 개선children을 prop으로 전달하는 대신 JSX 요소로 전달하세요.
-<IconText iconPath={<img src={Option} alt="추가 버튼" />} children="옵션" className="font-bold pl-2" /> +<IconText iconPath={<img src={Option} alt="추가 버튼" />} className="font-bold pl-2"> + 옵션 +</IconText>
113-113: React children 전달 방식 개선children을 prop으로 전달하는 대신 JSX 요소로 전달하세요.
-<IconText iconPath={<img src={Ticket} alt="추가 버튼" />} children="티켓" className="font-bold pl-2" /> +<IconText iconPath={<img src={Ticket} alt="추가 버튼" />} className="font-bold pl-2"> + 티켓 +</IconText>
22-27: 중복 코드 리팩토링배열 처리 로직이 중복되어 있습니다. 유틸리티 함수로 추출하여 재사용성을 높이세요.
별도의 유틸리티 함수 파일에 추가:
// utils/arrayHelpers.ts export const normalizeToArray = <T>(data: T | T[] | undefined): T[] => { if (!data) return []; return Array.isArray(data) ? data : [data]; };그리고 해당 부분을 다음과 같이 수정:
- const tickets = Array.isArray(ticketsData?.result) - ? ticketsData.result - : ticketsData?.result - ? [ticketsData.result] - : []; + const tickets = normalizeToArray(ticketsData?.result); - const options = Array.isArray(ticketOptionsData?.result) - ? ticketOptionsData.result - : ticketOptionsData?.result - ? [ticketOptionsData.result] - : []; + const options = normalizeToArray(ticketOptionsData?.result);Also applies to: 62-67
src/pages/dashboard/ui/ticket/TicketOptionCreatePage.tsx (1)
30-31: 편집 모드에 따른 UI 차별화편집 모드와 생성 모드에 따라 제목과 설명을 다르게 표시하세요.
- <div className="text-center text-xl font-bold mb-5">티켓 옵션 생성</div> - <p className="text-gray-400 text-sm mb-5">티켓 옵션을 생성할 수 있습니다.</p> + <div className="text-center text-xl font-bold mb-5"> + 티켓 옵션 {isEditing ? '수정' : '생성'} + </div> + <p className="text-gray-400 text-sm mb-5"> + 티켓 옵션을 {isEditing ? '수정' : '생성'}할 수 있습니다. + </p>src/features/ticket/model/ticketOptionReducer.ts (2)
24-24: 주석 개선 필요더 명확한 주석으로 코드의 의도를 전달하세요.
-// 각 케이스는 동작하는 함수 정의. 세부 작동은 dispatch +// 티켓 옵션 상태를 관리하는 리듀서 함수
104-129: 매직 넘버 제거하드코딩된 숫자 3을 상수로 추출하여 유지보수성을 향상시키세요.
파일 상단에 상수 추가:
const DEFAULT_OPTIONS_COUNT = 3;그리고 코드에서 사용:
singleOptions: { - options: Array(3).fill(''), + options: Array(DEFAULT_OPTIONS_COUNT).fill(''), }, multiOptions: { - options: Array(3).fill(''), + options: Array(DEFAULT_OPTIONS_COUNT).fill(''), },src/features/dashboard/ui/DraggableList.tsx (1)
36-42: 표시 포맷 헬퍼 함수가 유용합니다.영어 코드를 한국어로 변환하는 함수가 사용자 경험을 향상시킵니다. 다만 타입 안전성을 위해 개선할 수 있습니다.
-const getDisplayFormat = (format: string) => { +const getDisplayFormat = (format: 'SINGLE' | 'MULTIPLE' | 'TEXT') => { if (format === 'SINGLE') return '객관식'; if (format === 'MULTIPLE') return '여러개 선택'; if (format === 'TEXT') return '자유로운 텍스트'; - - return format; + return format; // 이 줄은 타입상 도달할 수 없음 };src/features/ticket/hooks/useTicketOptionHook.ts (2)
54-70: 삭제 성공 시 피드백 방식을 통일해주세요.다른 mutation들과 달리 삭제 성공 시에만
console.log를 사용하고 있습니다. 일관성을 위해alert를 사용하는 것이 좋겠습니다.onSuccess: (_, ticketOptionId) => { // 티켓 옵션 목록과 상세 정보 쿼리 리패칭 queryClient.invalidateQueries({ queryKey: ['ticketOptions', id] }); queryClient.invalidateQueries({ queryKey: ['ticketOptionDetail', ticketOptionId] }); - console.log('티켓 옵션이 성공적으로 삭제되었습니다.'); + alert('티켓 옵션이 성공적으로 삭제되었습니다.'); },
103-134: 부착/부착취소 훅의 에러 처리 방식을 개선해주세요.부착과 부착취소 훅에서 에러 발생 시
console.log를 사용하고 있는데, 사용자에게 더 명확한 피드백을 위해alert를 사용하는 것이 좋겠습니다.// useAttachTicketOptionMutation의 onError onError: () => { - console.log('티켓 옵션 부착에 실패했습니다. 다시 시도해주세요.'); + alert('티켓 옵션 부착에 실패했습니다. 다시 시도해주세요.'); }, // useDetachTicketOptionMutation의 onError onError: () => { - console.log('티켓에 부착된 티켓 옵션 부착 취소에 실패했습니다. 다시 시도해주세요.'); + alert('티켓에 부착된 티켓 옵션 부착 취소에 실패했습니다. 다시 시도해주세요.'); },
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (19)
src/app/Layout.tsx(1 hunks)src/app/routes/Router.tsx(1 hunks)src/app/routes/routes.ts(1 hunks)src/features/dashboard/ui/DragArea.tsx(2 hunks)src/features/dashboard/ui/DraggableList.tsx(3 hunks)src/features/event/ui/EventList.tsx(0 hunks)src/features/ticket/api/ticketOption.ts(1 hunks)src/features/ticket/hooks/useTicketOptionDnD.ts(1 hunks)src/features/ticket/hooks/useTicketOptionForm.ts(1 hunks)src/features/ticket/hooks/useTicketOptionHook.ts(1 hunks)src/features/ticket/model/ticketOption.ts(1 hunks)src/features/ticket/model/ticketOptionReducer.ts(1 hunks)src/features/ticket/ui/TicketOptionFormSection.tsx(1 hunks)src/features/ticket/ui/TicketOptionListSection.tsx(1 hunks)src/main.tsx(2 hunks)src/pages/dashboard/ui/ticket/TicketOptionCreatePage.tsx(1 hunks)src/pages/dashboard/ui/ticket/TicketOptionPage.tsx(2 hunks)src/pages/home/ui/MainPage.tsx(0 hunks)tsconfig.app.json(1 hunks)
💤 Files with no reviewable changes (2)
- src/features/event/ui/EventList.tsx
- src/pages/home/ui/MainPage.tsx
🧰 Additional context used
🧬 Code Graph Analysis (9)
src/app/routes/Router.tsx (1)
src/app/routes/routes.ts (1)
DASHBOARD_ROUTES(36-51)
src/features/ticket/hooks/useTicketOptionDnD.ts (1)
src/features/ticket/hooks/useTicketOptionHook.ts (2)
useAttachTicketOptionMutation(103-117)useDetachTicketOptionMutation(120-134)
src/features/ticket/hooks/useTicketOptionForm.ts (3)
src/features/ticket/model/ticketOption.ts (3)
State(4-22)Action(25-35)TicketOptionsType(46-56)src/features/ticket/model/ticketOptionReducer.ts (4)
State(135-135)Action(135-135)ticketOptionReducer(25-133)initialState(4-22)src/features/ticket/hooks/useTicketOptionHook.ts (3)
useCreateTicketOptionMutation(16-30)useModifyTicketOptionMutation(33-51)useGetTicketOptionDetail(94-100)
src/features/ticket/model/ticketOptionReducer.ts (1)
src/features/ticket/model/ticketOption.ts (2)
State(4-22)Action(25-35)
src/features/dashboard/ui/DragArea.tsx (2)
src/features/ticket/model/ticketOption.ts (1)
TicketOptionsType(46-56)design-system/ui/buttons/HorizontalCardButton.tsx (1)
HorizontalCardButton(12-40)
src/features/ticket/api/ticketOption.ts (3)
src/features/ticket/model/ticketOption.ts (2)
TicketOptionRequest(37-44)TicketOptionResponse(58-58)src/shared/types/api/http-client.ts (1)
axiosClient(6-13)src/shared/types/api/apiResponse.ts (1)
ApiResponse(1-5)
src/features/ticket/hooks/useTicketOptionHook.ts (2)
src/features/ticket/model/ticketOption.ts (2)
TicketOptionRequest(37-44)TicketOptionResponse(58-58)src/features/ticket/api/ticketOption.ts (8)
createTicketOption(6-9)modifyTicketOption(12-18)deleteTicketOption(21-24)getTicketOptions(27-30)getAttachedTicketOptions(33-36)getTicketOptionDetail(39-42)attachTicketOption(45-54)detachTicketOption(57-68)
src/features/dashboard/ui/DraggableList.tsx (1)
src/features/ticket/hooks/useTicketOptionHook.ts (2)
useDetachTicketOptionMutation(120-134)useDeleteTicketOptionMutation(54-70)
src/features/ticket/model/ticketOption.ts (2)
src/features/ticket/model/ticketOptionReducer.ts (2)
State(135-135)Action(135-135)src/shared/types/api/apiResponse.ts (1)
ApiResponse(1-5)
🪛 Biome (1.9.4)
tsconfig.app.json
[error] 13-13: JSON standard does not allow comments.
(parse)
[error] 14-14: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 14-14: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 14-14: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 14-14: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 15-15: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 15-15: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 15-15: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 15-15: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 16-16: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 16-16: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 16-16: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 16-16: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 17-17: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 17-17: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 17-17: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 17-17: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 18-18: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 18-18: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 18-18: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 18-18: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 19-19: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 19-19: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 19-19: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 19-20: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 20-20: JSON standard does not allow comments.
(parse)
[error] 21-21: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 21-21: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 21-21: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 21-21: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 22-22: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 22-22: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 22-22: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 22-22: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 23-23: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 23-23: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 23-23: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 23-23: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 24-24: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 24-24: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 24-24: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 24-24: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 25-25: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 25-25: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 25-25: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 25-25: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 26-26: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 26-26: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 26-28: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 29-29: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 30-30: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 30-30: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 30-33: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
[error] 33-34: End of file expected
Use an array for a sequence of values: [1, 2]
(parse)
src/pages/dashboard/ui/ticket/TicketOptionPage.tsx
[error] 48-48: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
[error] 84-84: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
[error] 117-118: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
src/features/ticket/api/ticketOption.ts
[error] 22-22: Don't use '{}' as a type.
Prefer explicitly define the object shape. '{}' means "any non-nullable value".
(lint/complexity/noBannedTypes)
[error] 23-23: Don't use '{}' as a type.
Prefer explicitly define the object shape. '{}' means "any non-nullable value".
(lint/complexity/noBannedTypes)
[error] 67-67: Don't use '{}' as a type.
Prefer explicitly define the object shape. '{}' means "any non-nullable value".
(lint/complexity/noBannedTypes)
🪛 ESLint
src/pages/dashboard/ui/ticket/TicketOptionPage.tsx
[error] 53-53: Unexpected any. Specify a different type.
(@typescript-eslint/no-explicit-any)
src/features/ticket/api/ticketOption.ts
[error] 21-21: The {} ("empty object") type allows any non-nullish value, including literals like 0 and "".
- If that's what you want, disable this lint rule with an inline comment or configure the 'allowObjectTypes' rule option.
- If you want a type meaning "any object", you probably want
objectinstead. - If you want a type meaning "any value", you probably want
unknowninstead.
(@typescript-eslint/no-empty-object-type)
[error] 22-22: The {} ("empty object") type allows any non-nullish value, including literals like 0 and "".
- If that's what you want, disable this lint rule with an inline comment or configure the 'allowObjectTypes' rule option.
- If you want a type meaning "any object", you probably want
objectinstead. - If you want a type meaning "any value", you probably want
unknowninstead.
(@typescript-eslint/no-empty-object-type)
[error] 60-60: The {} ("empty object") type allows any non-nullish value, including literals like 0 and "".
- If that's what you want, disable this lint rule with an inline comment or configure the 'allowObjectTypes' rule option.
- If you want a type meaning "any object", you probably want
objectinstead. - If you want a type meaning "any value", you probably want
unknowninstead.
(@typescript-eslint/no-empty-object-type)
[error] 61-61: The {} ("empty object") type allows any non-nullish value, including literals like 0 and "".
- If that's what you want, disable this lint rule with an inline comment or configure the 'allowObjectTypes' rule option.
- If you want a type meaning "any object", you probably want
objectinstead. - If you want a type meaning "any value", you probably want
unknowninstead.
(@typescript-eslint/no-empty-object-type)
src/features/ticket/model/ticketOption.ts
[error] 58-58: An interface declaring no members is equivalent to its supertype.
(@typescript-eslint/no-empty-object-type)
🔇 Additional comments (33)
tsconfig.app.json (3)
6-10:lib배열 멀티라인 포맷팅 적용
기능에는 변화가 없으며, 요소별로 줄을 나눠 가독성을 높였습니다.
26-28:types배열 멀티라인 포맷팅 적용
기능에는 변화가 없으며,types항목을 멀티라인으로 정리해 읽기 쉽게 했습니다.🧰 Tools
🪛 Biome (1.9.4)
[error] 26-26: End of file expected
Use an array for a sequence of values:
[1, 2](parse)
[error] 26-26: End of file expected
Use an array for a sequence of values:
[1, 2](parse)
[error] 26-28: End of file expected
Use an array for a sequence of values:
[1, 2](parse)
30-33:include배열 멀티라인 포맷팅 적용
기능에는 변화가 없으며, 프로젝트 포함 경로를 멀티라인으로 정렬해 유지보수를 용이하게 했습니다.🧰 Tools
🪛 Biome (1.9.4)
[error] 30-30: End of file expected
Use an array for a sequence of values:
[1, 2](parse)
[error] 30-30: End of file expected
Use an array for a sequence of values:
[1, 2](parse)
[error] 30-33: End of file expected
Use an array for a sequence of values:
[1, 2](parse)
src/app/routes/Router.tsx (1)
77-77:Details
❓ Verification inconclusive
인증 요구사항 확인 필요
티켓 옵션 편집 페이지가
requiresAuth: false로 설정되어 있습니다. 티켓 옵션 편집이 인증 없이 접근 가능한 기능인지 확인이 필요합니다.다음 스크립트로 다른 대시보드 라우트들의 인증 설정을 확인해보세요:
🏁 Script executed:
#!/bin/bash # 대시보드 관련 라우트들의 인증 설정 확인 grep -A 1 -B 1 "DASHBOARD_ROUTES\." src/app/routes/Router.tsx | grep "requiresAuth"Length of output: 1407
인증 설정 일관성 확인 필요
현재
src/app/routes/Router.tsx의 모든 대시보드 관련 라우트에requiresAuth: false가 설정되어 있으며,ticketOptionEdit도 동일하게 동작합니다.
- 다른 페이지들과 인증 설정이 일관적으로
false로 적용되어 있습니다.요구사항에 따라 대시보드 기능 전체에 인증이 필요하다면, 모든 해당 라우트의
requiresAuth를true로 변경해야 합니다.
디자인 상 이 라우트들이 인증 없이 접근 가능한 것이 맞는지 확인 부탁드립니다.src/app/Layout.tsx (1)
3-8: 좋은 구조적 개선입니다불필요한 div 중첩을 제거하여 JSX 구조를 단순화한 것은 좋은 리팩토링입니다. 코드 가독성이 향상되었고 동일한 기능을 더 깔끔하게 구현했습니다.
src/features/ticket/ui/TicketOptionListSection.tsx (1)
8-10: 컴포넌트 구조가 깔끔합니다!
useTicketOptionForm훅의 반환 타입을 활용한 props 정의가 적절합니다.src/features/ticket/model/ticketOption.ts (1)
3-35: State와 Action 타입 정의가 명확합니다!주석이 잘 작성되어 있고, 액션 타입이 체계적으로 정의되어 있습니다.
src/features/ticket/ui/TicketOptionFormSection.tsx (1)
51-55: 응답 형식 선택 시 상태 초기화가 적절합니다!선택 변경 시 관련 경고와 포커스 상태를 초기화하는 로직이 잘 구현되어 있습니다.
src/features/dashboard/ui/DragArea.tsx (5)
6-7: 새로운 의존성 추가가 적절합니다.
TicketOptionsType모델과useParams훅 추가로 타입 안전성과 라우팅 기능이 향상되었습니다.
10-15: Props 구조 단순화가 훌륭합니다.복잡한
dataprop을 단순한options배열로 변경하여 컴포넌트의 사용성과 가독성이 크게 향상되었습니다.activeButton과ticketNameprops 추가도 적절합니다.
17-21: 기본값 설정과 라우팅 로직이 적절합니다.
ticketSurveyAddButton의 기본값을false로 변경한 것이 합리적입니다isTicketArea조건에서startsWith('ticket-')체크 추가로 더 유연한 매칭이 가능해졌습니다useParams를 통한id추출이 적절합니다
38-51: 렌더링 로직 개선이 우수합니다.
options배열을 직접 매핑하는 방식으로 변경되어 코드가 더 직관적이고 효율적이 되었습니다.DraggableList에 전달되는 props들이TicketOptionsType인터페이스와 잘 매치됩니다.
57-60: 버튼 텍스트와 네비게이션 경로 업데이트가 적절합니다.
- "티켓 설문"에서 "티켓 옵션"으로 용어 통일이 좋습니다
- 동적
id파라미터를 포함한 새로운 라우팅 경로가 적절히 적용되었습니다src/features/ticket/api/ticketOption.ts (4)
6-9: 티켓 옵션 생성 함수가 잘 구현되었습니다.API 엔드포인트와 타입 정의가 적절하며, Promise 기반 비동기 처리가 올바르게 구현되었습니다.
27-36: 조회 함수들이 잘 구현되었습니다.티켓 옵션 목록 조회와 티켓에 부착된 옵션 조회 함수들이 적절하게 구현되었습니다. API 엔드포인트 경로도 RESTful 설계 원칙을 잘 따르고 있습니다.
39-42: 상세 조회 함수가 잘 구현되었습니다.단일 티켓 옵션 상세 정보 조회가 적절하게 구현되었습니다.
45-54: 부착 함수 구현이 적절합니다.POST 요청으로 쿼리 파라미터를 사용하는 방식이 적절하며, 빈 body와 함께 params 옵션을 사용한 구현이 올바릅니다.
src/features/dashboard/ui/DraggableList.tsx (8)
5-6: 새로운 의존성 추가가 적절합니다.React Router 훅과 ticket option mutation 훅들을 추가하여 컴포넌트의 자립성이 향상되었습니다.
9-18: Props 인터페이스 개선이 우수합니다.
idprop을optionId와draggableId로 분리하여 명확성 향상activeButtonprop 추가로 UI 제어 유연성 확보- 외부 의존성 제거로 컴포넌트 자립성 향상
31-34: mutation 훅 사용이 적절합니다.외부 콜백 대신 React Query mutation 훅을 직접 사용하여 컴포넌트의 독립성과 재사용성이 향상되었습니다.
45-51: 부착 취소 핸들러가 잘 구현되었습니다.droppableId에서 ticketId를 추출하는 로직과 mutation 호출이 적절하게 구현되었습니다.
54-60: 편집 핸들러 개선이 좋습니다.localStorage 사용을 제거하고 라우트 파라미터와 state를 활용하는 방식으로 변경된 것이 더 명확하고 안전합니다.
63-68: 삭제 핸들러가 적절하게 구현되었습니다.options 영역에서만 삭제가 가능하도록 하는 조건부 로직이 적절합니다.
86-95: 조건부 버튼 렌더링이 우수합니다.
activeButtonprop에 따라 수정 또는 삭제 버튼을 선택적으로 표시하는 로직이 깔끔하고 유연합니다.
106-110: 티켓 영역의 삭제 버튼 구현이 적절합니다.티켓 영역에서는 삭제가 아닌 부착 취소를 수행하도록 하는 로직이 올바르게 구현되었습니다.
src/features/ticket/hooks/useTicketOptionHook.ts (8)
1-14: import 문과 타입 정의가 적절합니다.필요한 모든 의존성이 올바르게 import되었고, API 함수와 타입들이 적절히 가져와졌습니다.
16-30: 생성 mutation 훅이 잘 구현되었습니다.eventId를 URL 파라미터에서 자동으로 추출하여 요청에 포함하는 방식이 깔끔합니다. 성공/실패 시 사용자 피드백과 네비게이션 처리도 적절합니다.
33-51: 수정 mutation 훅이 우수합니다.쿼리 무효화를 통한 캐시 일관성 유지와 적절한 사용자 피드백이 잘 구현되었습니다. 관련 쿼리들을 모두 무효화하는 것도 좋습니다.
73-82: 티켓 옵션 목록 조회 훅이 적절합니다.쿼리 키 전략과 enabled 조건이 올바르게 설정되어 있습니다.
85-91: 부착된 옵션 조회 훅이 잘 구현되었습니다.ticketId를 파라미터로 받아 조건부로 활성화하는 방식이 적절합니다.
94-100: 상세 조회 훅이 적절합니다.ticketOptionId 조건부 활성화가 올바르게 구현되었습니다.
103-117: 부착 mutation 훅이 잘 구현되었습니다.적절한 쿼리 무효화와 로깅을 통한 피드백이 구현되어 있습니다.
120-134: 부착 취소 mutation 훅이 적절합니다.부착과 일관된 구조로 구현되어 있으며, 적절한 쿼리 무효화가 수행됩니다.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (5)
src/features/ticket/hooks/useTicketOptionForm.ts (4)
30-41: 최소 옵션 개수 검증 로직 수정 필요현재 로직은 옵션이 0개일 때만 삭제를 막지만, 최소 1개 이상 유지하려면 1개일 때도 삭제를 막아야 합니다.
-if (activeOptions.options.length < 1) { +if (activeOptions.options.length <= 1) {
103-105: 응답 형식 타입 변환 필요
optionDetail.type은 영문('SINGLE', 'MULTIPLE', 'TEXT')이지만,SET_RESPONSE_TOGGLE은 한글 형식을 기대합니다.타입 변환을 추가하세요:
-dispatch({ type: 'SET_RESPONSE_TOGGLE', payload: optionDetail.type }); +const responseFormat = optionDetail.type === 'SINGLE' ? '객관식' + : optionDetail.type === 'MULTIPLE' ? '여러개 선택' + : '자유로운 텍스트'; +dispatch({ type: 'SET_RESPONSE_TOGGLE', payload: responseFormat });또는
SET_ALL액션을 사용하는 것을 고려하세요.
142-142: 디버그용 console.log 제거 필요프로덕션 코드에 디버그 로그가 남아있습니다.
-console.log('Clicked!');
153-153: querySelector 대신 React ref 사용 권장한글 클래스명을 사용한 직접적인 DOM 조작은 취약합니다. React의 ref를 사용하는 것이 더 안전합니다.
다음과 같은 방법을 고려하세요:
useRef를 사용하여 요소 참조 저장- 스크롤 대상 컴포넌트에 ref 전달
ref.current?.scrollIntoView()사용또한 '.질문-입력란' 같은 한글 클래스명보다는 'question-input' 같은 영문 클래스명이나 data 속성 사용을 권장합니다.
src/features/ticket/model/ticketOption.ts (1)
60-60: 빈 인터페이스 대신 타입 별칭 사용 권장ESLint가 올바르게 지적한 대로, 추가 속성이 없는 인터페이스는 타입 별칭으로 대체하는 것이 좋습니다.
다음과 같이 수정하세요:
-export interface TicketOptionResponse extends ApiResponse<TicketOptionsType> {} +export type TicketOptionResponse = ApiResponse<TicketOptionsType>;🧰 Tools
🪛 ESLint
[error] 60-60: An interface declaring no members is equivalent to its supertype.
(@typescript-eslint/no-empty-object-type)
🧹 Nitpick comments (4)
src/features/ticket/hooks/useTicketOptionForm.ts (1)
181-193: 중복 로직 리팩터링 권장타입 매핑과 choices 필터링 로직이 반복됩니다. 헬퍼 함수로 추출하는 것을 고려하세요.
+ const getChoicesForType = (responseFormat: string) => { + if (responseFormat === 'TEXT') return []; + + const options = responseFormat === 'SINGLE' + ? state.singleOptions.options + : state.multiOptions.options; + + return options.filter(opt => opt.trim() !== ''); + }; if (isValid) { - const type = - state.question.responseFormat === 'SINGLE' - ? 'SINGLE' - : state.question.responseFormat === 'MULTIPLE' - ? 'MULTIPLE' - : 'TEXT'; - - const choices = - state.question.responseFormat === 'SINGLE' - ? state.singleOptions.options.filter(opt => opt.trim() !== '') - : state.question.responseFormat === 'MULTIPLE' - ? state.multiOptions.options.filter(opt => opt.trim() !== '') - : []; + const type = state.question.responseFormat; + const choices = getChoicesForType(type);src/features/ticket/model/ticketOption.ts (1)
43-43: TicketOptionRequest의 type 필드 타입 개선
type필드가string으로 정의되어 있지만,TicketOptionType을 사용하면 타입 안전성이 향상됩니다.export interface TicketOptionRequest { eventId: number; name: string; description: string; - type: string; + type: TicketOptionType; isMandatory: boolean; choices: string[]; }src/features/ticket/model/ticketOptionReducer.ts (2)
57-57: 타입 단언 대신 타입 가드 사용 권장
action.payload as TicketOptionType는 런타임에서 타입 안전성을 보장하지 않습니다. 타입 가드를 사용하는 것을 고려하세요.+ const isValidTicketOptionType = (value: string): value is TicketOptionType => { + return ['SINGLE', 'MULTIPLE', 'TEXT'].includes(value); + }; + case 'SET_RESPONSE_TOGGLE': + if (!isValidTicketOptionType(action.payload)) { + console.warn(`Invalid ticket option type: ${action.payload}`); + return state; + } return { ...state, question: { ...state.question, - responseFormat: action.payload as TicketOptionType, + responseFormat: action.payload, }, };
115-117: 불필요한 타입 변환 로직 제거 가능
type이 이미TicketOptionType이라면 responseFormat 변환 로직이 불필요합니다.case 'SET_ALL': { const { name, description, type, isMandatory, choices } = action.payload; - // type에 따라 responseFormat 한글로 변환 - let responseFormat = 'SINGLE'; - if (type === 'MULTIPLE') responseFormat = 'MULTIPLE'; - if (type === 'TEXT') responseFormat = 'TEXT'; return { ...state, question: { ...state.question, title: name, description: description, - responseFormat: responseFormat as TicketOptionType, + responseFormat: type, answerToggled: isMandatory, },
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
src/features/dashboard/ui/DraggableList.tsx(2 hunks)src/features/ticket/api/ticketOption.ts(1 hunks)src/features/ticket/hooks/useTicketOptionDnD.ts(1 hunks)src/features/ticket/hooks/useTicketOptionForm.ts(1 hunks)src/features/ticket/model/ticketOption.ts(1 hunks)src/features/ticket/model/ticketOptionReducer.ts(1 hunks)src/features/ticket/ui/TicketOptionFormSection.tsx(1 hunks)src/features/ticket/ui/TicketOptionListSection.tsx(1 hunks)src/main.tsx(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
- src/features/ticket/ui/TicketOptionFormSection.tsx
- src/features/ticket/ui/TicketOptionListSection.tsx
- src/main.tsx
- src/features/ticket/hooks/useTicketOptionDnD.ts
- src/features/ticket/api/ticketOption.ts
- src/features/dashboard/ui/DraggableList.tsx
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/features/ticket/model/ticketOptionReducer.ts (1)
src/features/ticket/model/ticketOption.ts (3)
State(6-24)Action(27-37)TicketOptionType(3-3)
src/features/ticket/hooks/useTicketOptionForm.ts (3)
src/features/ticket/model/ticketOptionReducer.ts (4)
State(143-143)Action(143-143)ticketOptionReducer(25-141)initialState(4-22)src/features/ticket/model/ticketOption.ts (3)
State(6-24)Action(27-37)TicketOptionsType(48-58)src/features/ticket/hooks/useTicketOptionHook.ts (3)
useCreateTicketOptionMutation(16-30)useModifyTicketOptionMutation(33-51)useGetTicketOptionDetail(94-100)
src/features/ticket/model/ticketOption.ts (2)
src/features/ticket/model/ticketOptionReducer.ts (2)
State(143-143)Action(143-143)src/shared/types/api/apiResponse.ts (1)
ApiResponse(1-5)
🪛 ESLint
src/features/ticket/model/ticketOption.ts
[error] 60-60: An interface declaring no members is equivalent to its supertype.
(@typescript-eslint/no-empty-object-type)
🔇 Additional comments (2)
src/features/ticket/model/ticketOptionReducer.ts (2)
82-84: 인덱스 범위 검증 추가 완료!이전 검토에서 제안된 배열 인덱스 범위 검증이 적절히 구현되었습니다. 런타임 에러를 방지하는 좋은 개선입니다.
Also applies to: 101-103
120-120: choices 배열 안전성 검증 완료!
Array.isArray(choices)검증을 통해 choices가 배열이 아닌 경우를 안전하게 처리하고 있습니다. 이전 검토 의견이 잘 반영되었습니다.
8cf78b3 to
b510ece
Compare
티켓 옵션 및 부착된 티켓 옵션 조회
티켓 옵션 생성
티켓 옵션 상세 조회 및 수정
티켓 옵션 삭제
티켓 옵션 부착 및 부착 취소
기타 사항
Summary by CodeRabbit
신규 기능
버그 수정 및 리팩터링
문서 및 스타일