Conversation
|
""" Walkthrough이 PR은 티켓 생성 기능 관련 전반적인 코드 개선을 포함합니다. Changes
Sequence Diagram(s)sequenceDiagram
participant U as "사용자"
participant P as "TicketCreatePage"
participant D as "TicketDatePicker"
participant API as "createTicket API"
U->>P: 티켓 정보 입력 (일반/날짜 선택)
P->>D: 날짜 선택 업데이트 요청
D-->>P: onDateChange 콜백 전달
U->>P: 저장 버튼 클릭
P->>API: 티켓 데이터 전송 (ticketData)
API-->>P: 응답 데이터 반환
Possibly related issues
✨ Finishing Touches
🪧 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
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (10)
src/features/ticket/api/ticket.ts (1)
1-7: 코드가 깔끔하고 기본 패턴을 잘 따르고 있습니다.API 함수가 간결하게 구현되어 있으며 필요한 타입과 의존성을 올바르게 가져오고 있습니다. POST 요청 처리와 응답 반환이 적절합니다.
다만 에러 처리를 강화하기 위해 try-catch 블록을 추가하는 것을 고려해 보세요. 현재는 오류가 자동으로 상위 컴포넌트로 전파됩니다.
export const createTicket = async(data: CreateTicketRequest) => { + try { const response = await axiosClient.post('/tickets', data); return response.data; + } catch (error) { + // 구체적인 오류 처리 또는 로깅 + console.error('티켓 생성 중 오류 발생:', error); + throw error; // 필요에 따라 상위 컴포넌트로 오류 다시 전파 + } }src/features/ticket/model/ticketCreation.ts (1)
1-12: 인터페이스가 잘 정의되어 있습니다.티켓 생성에 필요한 모든 속성이 명확하게 정의되어 있으며 각 속성의 타입이 적절합니다.
몇 가지 개선 사항을 고려해 볼 수 있습니다:
- 날짜와 시간 형식에 대한 주석을 추가하면 좋을 것 같습니다.
export interface CreateTicketRequest { eventId: number; ticketType: string; ticketName: string; ticketDescription: string; ticketPrice: number; availableQuantity: number; + // 'YYYY-MM-DD' 형식 startDate: string; + // 'YYYY-MM-DD' 형식 endDate: string; + // 'HH:MM' 형식 startTime: string; + // 'HH:MM' 형식 endTime: string; }
- 필요한 경우 나중에 확장성을 고려하여 일부 필드를 선택적으로 만들거나 유니온 타입을 사용하여 더 제한적인 타입을 정의할 수 있습니다.
src/features/ticket/model/TicketDatePicker.tsx (3)
15-22: 컴포넌트 초기화 및 상태 관리가 잘 구현되어 있습니다.Props와 상태 초기화가 적절하게 구현되어 있으며, 기본값 제공이 잘 되어 있습니다.
날짜 선택 후 유효성 검증을 추가하면 더 좋을 것 같습니다. 예를 들어, 종료 날짜가 시작 날짜보다 이전인 경우를 방지할 수 있습니다.
const [endDate, setEndDate] = useState<Date | null>(ticketState?.endDate ? new Date(ticketState.endDate) : new Date()); + // 종료 날짜가 시작 날짜보다 이전이면 시작 날짜로 설정 + useEffect(() => { + if (startDate && endDate && endDate < startDate) { + setEndDate(startDate); + } + }, [startDate, endDate]);
45-75: useEffect 구현에 대한 피드백useEffect에서 상태 업데이트 로직이 복잡해 보입니다. 조건부 렌더링을 사용하여 불필요한 상태 업데이트를 방지하는 것은 좋은 접근 방식입니다.
다만 의존성 배열에 많은 항목이 있어 렌더링 성능에 영향을 줄 수 있습니다. 의존성을 줄이기 위해 콜백 함수를 메모이제이션하는 것을 고려해 보세요:
+ const updateTicketState = useCallback(() => { + if (setTicketState) { + setTicketState(prev => { + const newStartDate = startDate ? formatDate(startDate) : ''; + const newEndDate = endDate ? formatDate(endDate) : ''; + if ( + prev.startDate !== newStartDate || + prev.endDate !== newEndDate || + prev.startTime !== startTime || + prev.endTime !== endTime + ) { + onDateChange({ + startDate: newStartDate, + endDate: newEndDate, + startTime, + endTime, + }); + return { + ...prev, + startDate: newStartDate, + endDate: newEndDate, + startTime, + endTime, + }; + } + return prev; + }); + } + }, [startDate, endDate, startTime, endTime, setTicketState, onDateChange]); + + useEffect(() => { + updateTicketState(); + }, [updateTicketState]);
77-173: UI 구현이 잘되어 있지만 접근성을 개선할 수 있습니다.UI 컴포넌트가 반응형으로 잘 구현되어 있으며, 날짜 선택기 커스터마이징도 잘 되어 있습니다.
접근성 개선을 위해 몇 가지 수정을 제안합니다:
- 라벨과 입력 필드의 연결을 위한 레이블 요소 추가:
- {!isLabel && <span className="text-sm font-medium">시작 날짜</span>} + {!isLabel && <label htmlFor="startDate" className="text-sm font-medium">시작 날짜</label>}
- 시간 선택 드롭다운에도 레이블 추가:
+ <label htmlFor="startTime" className="sr-only">시작 시간</label> <select id="startTime" value={startTime} onChange={e => setStartTime(e.target.value)} className="w-20 h-9 md:w-24 md:h-10 border border-placeholderText text-sm md:text-md rounded-[5px] p-2" aria-label="시작 시간" >
- 버튼에 접근성 레이블 추가:
- <button onClick={decreaseMonth} disabled={prevMonthButtonDisabled} className="mb-1"> + <button + onClick={decreaseMonth} + disabled={prevMonthButtonDisabled} + className="mb-1" + aria-label="이전 달" + >src/pages/dashboard/ui/ticket/TicketCreatePage.tsx (5)
24-29: 중복된 상태 관리에 주의하세요.
eventState와ticketData내의 날짜/시간 필드가 중복으로 관리되고 있습니다. 이런 구조는 두 상태가 동기화되지 않을 위험이 있습니다.가능하다면 하나의 상태만 사용하거나,
ticketData에서 날짜 정보를 파생시키는 방식으로 개선하는 것이 좋겠습니다.- const [eventState, setEventState] = useState({ - startDate: '', - endDate: '', - startTime: '', - endTime: '', - }); + // TicketDatePicker 컴포넌트를 위한 상태는 ticketData에서 파생 + const eventState = { + startDate: ticketData.startDate, + endDate: ticketData.endDate, + startTime: ticketData.startTime, + endTime: ticketData.endTime, + };
31-42: 티켓 타입 매핑이 하드코딩되어 있습니다.티켓 타입을 매핑하는 방식이 하드코딩되어 있어 유지보수가 어려울 수 있습니다.
상수나 Enum을 사용하여 매핑하는 것이 더 명확하고 유지보수하기 좋습니다.
+ const TICKET_TYPE_MAPPING = { + '선착순': 'FIRST_COME', + '선정제': 'SELECTION' + }; const handleTicketTypeChange = (type: string) => { - let mappedType: string; - if (type === '선착순') { - mappedType = 'FIRST_COME'; - } else { - mappedType = 'SELECTION'; - } + const mappedType = TICKET_TYPE_MAPPING[type] || 'FIRST_COME'; setTicketData((prev) => ({ ...prev, ticketType: mappedType, })); };
66-66: 예상 수익 계산에 검증이 필요합니다.가격이나 수량 값이 유효하지 않을 경우(예: undefined)에 대한 처리가 없습니다.
- const sum = ticketData.ticketPrice * ticketData.availableQuantity; + const sum = (ticketData.ticketPrice || 0) * (ticketData.availableQuantity || 0);
146-148: 디버그 코드가 남아있습니다.
ticketData상태를 화면에 출력하는 디버그 코드가 있습니다. 이는 개발 과정에서만 필요하며 프로덕션 코드에는 포함되지 않아야 합니다.- <div className="ticket-data-output"> - <pre>{JSON.stringify(ticketData, null, 2)}</pre> - </div>
151-151: 저장 버튼에 로딩 상태 표시가 필요합니다.API 호출 중에는 버튼의 로딩 상태를 표시하는 것이 좋습니다.
+ const [isLoading, setIsLoading] = useState(false); const handleSaveClick = async () => { + setIsLoading(true); try { const response = await createTicket(ticketData); console.log('티켓 저장 성공:', response); } catch (err) { console.error('티켓 저장에 실패했습니다.', err); } finally { + setIsLoading(false); } }; - <Button label="저장하기" onClick={handleSaveClick} className="w-full h-12 rounded-full" /> + <Button label="저장하기" onClick={handleSaveClick} className="w-full h-12 rounded-full" loading={isLoading} />
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (5)
src/features/ticket/api/ticket.ts(1 hunks)src/features/ticket/model/TicketContext.tsx(1 hunks)src/features/ticket/model/TicketDatePicker.tsx(1 hunks)src/features/ticket/model/ticketCreation.ts(1 hunks)src/pages/dashboard/ui/ticket/TicketCreatePage.tsx(6 hunks)
🧰 Additional context used
🧬 Code Definitions (2)
src/features/ticket/api/ticket.ts (1)
src/features/ticket/model/ticketCreation.ts (1)
CreateTicketRequest(1-12)
src/features/ticket/model/TicketDatePicker.tsx (1)
src/features/ticket/model/TicketContext.tsx (1)
TicketState(3-12)
🔇 Additional comments (4)
src/features/ticket/model/TicketContext.tsx (2)
16-33: TicketProvider 구현이 적절합니다.상태 초기화와 컨텍스트 제공이 잘 구현되어 있습니다. 기본 시간값을 설정한 것이 좋습니다.
setTicketChannelId함수에서는ticketChannelId를 추가하고 있는데, 이 부분이 앞서 지적한 타입 정의 문제와 관련이 있습니다. 인터페이스를 수정하면 이 부분의 타입 안전성도 확보됩니다.
35-41: 커스텀 훅 구현이 적절합니다.
useTicketState훅이 컨텍스트 사용을 단순화하고, 컨텍스트가 Provider 내부에서 사용되는지 확인하는 유효성 검사를 포함하고 있습니다.src/pages/dashboard/ui/ticket/TicketCreatePage.tsx (2)
7-9: 새로운 기능을 위한 필요한 임포트들이 추가되었습니다.적절한 컴포넌트와 API 함수를 임포트하여 티켓 생성 기능을 구현하기 위한 준비가 잘 되었습니다.
53-63: 날짜 변경 처리가 중복되어 있습니다.
handleDateChange함수에서 동일한 데이터를 두 상태에 모두 업데이트하고 있습니다.이전 코멘트에서 언급한 대로 상태 관리 방식을 개선하면 이 함수도 단순화할 수 있을 것입니다.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/pages/dashboard/ui/ticket/TicketCreatePage.tsx (1)
12-23:⚠️ Potential issue이벤트 ID가 하드코딩되어 있습니다.
eventId가1로 하드코딩되어 있습니다. PR 요약에서 언급했듯이 "이벤트 ID 연결이 아직 구현되지 않음"이라고 하더라도, 상수로 하드코딩하는 것보다 환경 변수나 설정 파일에서 가져오는 방식이 더 유지보수에 좋습니다.const [ticketData, setTicketData] = useState<CreateTicketRequest>({ - eventId: 1, + eventId: Number(new URLSearchParams(window.location.search).get('eventId')) || 0, ticketType: 'FIRST_COME', ticketName: '', ticketDescription: '', ticketPrice: 0, availableQuantity: 0, startDate: '', endDate: '', startTime: '', endTime: '', });
🧹 Nitpick comments (3)
src/features/ticket/model/TicketContext.tsx (2)
13-24: 초기 상태값 설정이 불완전합니다.날짜 관련 초기값에 일관성이 없습니다.
startTime과endTime에는 기본값이 설정되어 있지만startDate와endDate는 빈 문자열로 초기화되어 있습니다. 사용자 경험 측면에서 날짜도 기본값을 설정하거나, 모두 빈 값으로 통일하는 것이 좋습니다.const [ticketState, setTicketState] = useState<CreateTicketRequest>({ eventId: 0, ticketType: '', ticketName: '', ticketDescription: '', ticketPrice: 0, availableQuantity: 0, - startDate: '', - endDate: '', + startDate: new Date().toISOString().split('T')[0], // 오늘 날짜를 기본값으로 설정 + endDate: new Date().toISOString().split('T')[0], // 오늘 날짜를 기본값으로 설정 startTime: '06:00', endTime: '23:00', });
37-43: 에러 메시지의 현지화가 필요합니다.
useTicketState훅의 에러 메시지가 영어로 작성되어 있습니다. 한국어 UI에서 일관성을 유지하기 위해 에러 메시지도 한국어로 작성하는 것이 좋습니다.export const useTicketState = () => { const context = useContext(TicketContext); if (!context) { - throw new Error('useTicketState must be used within a TicketProvider'); + throw new Error('useTicketState는 반드시 TicketProvider 내에서 사용되어야 합니다'); } return context; };src/pages/dashboard/ui/ticket/TicketCreatePage.tsx (1)
25-36: 티켓 타입 매핑 로직을 더 간결하게 작성할 수 있습니다.현재 if-else 구문을 사용하여 티켓 타입을 매핑하고 있는데, 이 로직은 객체 매핑이나 삼항 연산자를 사용하여 더 간결하게 작성할 수 있습니다.
const handleTicketTypeChange = (type: string) => { - let mappedType: string; - if (type === '선착순') { - mappedType = 'FIRST_COME'; - } else { - mappedType = 'SELECTION'; - } + const typeMap: Record<string, string> = { + '선착순': 'FIRST_COME', + '선정': 'SELECTION' + }; + const mappedType = typeMap[type] || 'FIRST_COME'; setTicketData((prev) => ({ ...prev, ticketType: mappedType, })); };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/features/ticket/model/TicketContext.tsx(1 hunks)src/features/ticket/model/TicketDatePicker.tsx(1 hunks)src/pages/dashboard/ui/ticket/TicketCreatePage.tsx(6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/features/ticket/model/TicketDatePicker.tsx
🔇 Additional comments (3)
src/features/ticket/model/TicketContext.tsx (1)
4-8: 타입 안전성을 위한 인터페이스 개선이 필요합니다.
setTicketChannelId함수가ticketChannelId속성을 추가하고 있으나, 이 속성이CreateTicketRequest타입에 정의되어 있는지 확인이 필요합니다. 타입 안전성을 보장하기 위해CreateTicketRequest인터페이스에 이 속성을 추가해야 합니다.#!/bin/bash # ticketCreation.ts 파일에서 CreateTicketRequest 인터페이스 확인 find src -name "ticketCreation.ts" -exec cat {} \;src/pages/dashboard/ui/ticket/TicketCreatePage.tsx (2)
41-43: 유효성 검사 로직이 적절히 구현되었습니다.음수 값을 방지하는 유효성 검사 로직이 적절히 구현되었습니다. 이전 리뷰 피드백이 반영된 것으로 보입니다.
144-145:TicketDatePicker컴포넌트 활용이 적절합니다.날짜 선택기 컴포넌트를 적절히 활용하여 날짜와 시간 데이터를 관리하고 있습니다. 이벤트 상태 관리와 분리된 새로운 컴포넌트를 만들었다는 PR 요약에 맞게 잘 구현되었습니다.
| const handleSaveClick = async () => { | ||
| if (!ticketData.ticketName || !ticketData.ticketDescription || ticketData.ticketPrice < 0 || !ticketData.availableQuantity) { | ||
| alert('모든 필수 입력 항목을 작성해주세요.'); | ||
| return; | ||
| } | ||
| try { | ||
| const response = await createTicket(ticketData); | ||
| console.log('티켓 저장 성공:', response); | ||
| alert('티켓이 성공적으로 저장되었습니다.'); | ||
| } catch (err) { | ||
| console.error('티켓 저장에 실패했습니다.', err); | ||
| alert('티켓 저장에 실패했습니다. 다시 시도해주세요.'); | ||
| } | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
폼 유효성 검사가 불완전합니다.
handleSaveClick 함수에서 필수 필드에 대한 유효성 검사를 수행하고 있지만, 날짜와 시간 필드(startDate, endDate, startTime, endTime)에 대한 검사가 누락되어 있습니다. 또한 유효성 검사에서 ticketPrice < 0만 체크하고 있는데, 가격이 0이어도 괜찮은지 비즈니스 로직을 확인해야 합니다.
const handleSaveClick = async () => {
- if (!ticketData.ticketName || !ticketData.ticketDescription || ticketData.ticketPrice < 0 || !ticketData.availableQuantity) {
+ if (!ticketData.ticketName || !ticketData.ticketDescription || ticketData.ticketPrice < 0 ||
+ !ticketData.availableQuantity || !ticketData.startDate || !ticketData.endDate ||
+ !ticketData.startTime || !ticketData.endTime) {
alert('모든 필수 입력 항목을 작성해주세요.');
return;
}
+
+ // 날짜 유효성 검사
+ const start = new Date(`${ticketData.startDate}T${ticketData.startTime}`);
+ const end = new Date(`${ticketData.endDate}T${ticketData.endTime}`);
+
+ if (isNaN(start.getTime()) || isNaN(end.getTime())) {
+ alert('유효하지 않은 날짜 또는 시간 형식입니다.');
+ return;
+ }
+
+ if (start >= end) {
+ alert('종료 일시는 시작 일시보다 이후여야 합니다.');
+ return;
+ }
try {
const response = await createTicket(ticketData);
console.log('티켓 저장 성공:', response);
alert('티켓이 성공적으로 저장되었습니다.');
} catch (err) {
console.error('티켓 저장에 실패했습니다.', err);
alert('티켓 저장에 실패했습니다. 다시 시도해주세요.');
}
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handleSaveClick = async () => { | |
| if (!ticketData.ticketName || !ticketData.ticketDescription || ticketData.ticketPrice < 0 || !ticketData.availableQuantity) { | |
| alert('모든 필수 입력 항목을 작성해주세요.'); | |
| return; | |
| } | |
| try { | |
| const response = await createTicket(ticketData); | |
| console.log('티켓 저장 성공:', response); | |
| alert('티켓이 성공적으로 저장되었습니다.'); | |
| } catch (err) { | |
| console.error('티켓 저장에 실패했습니다.', err); | |
| alert('티켓 저장에 실패했습니다. 다시 시도해주세요.'); | |
| } | |
| }; | |
| const handleSaveClick = async () => { | |
| if ( | |
| !ticketData.ticketName || | |
| !ticketData.ticketDescription || | |
| ticketData.ticketPrice < 0 || | |
| !ticketData.availableQuantity || | |
| !ticketData.startDate || | |
| !ticketData.endDate || | |
| !ticketData.startTime || | |
| !ticketData.endTime | |
| ) { | |
| alert('모든 필수 입력 항목을 작성해주세요.'); | |
| return; | |
| } | |
| // 날짜 유효성 검사 | |
| const start = new Date(`${ticketData.startDate}T${ticketData.startTime}`); | |
| const end = new Date(`${ticketData.endDate}T${ticketData.endTime}`); | |
| if (isNaN(start.getTime()) || isNaN(end.getTime())) { | |
| alert('유효하지 않은 날짜 또는 시간 형식입니다.'); | |
| return; | |
| } | |
| if (start >= end) { | |
| alert('종료 일시는 시작 일시보다 이후여야 합니다.'); | |
| return; | |
| } | |
| try { | |
| const response = await createTicket(ticketData); | |
| console.log('티켓 저장 성공:', response); | |
| alert('티켓이 성공적으로 저장되었습니다.'); | |
| } catch (err) { | |
| console.error('티켓 저장에 실패했습니다.', err); | |
| alert('티켓 저장에 실패했습니다. 다시 시도해주세요.'); | |
| } | |
| }; |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (4)
src/features/ticket/api/ticket.ts (1)
1-7: 새로운 티켓 생성 함수 구현 확인
간단하게 Axios POST 요청을 수행하여response.data를 반환하는 구조로 보입니다. 에러 처리가 별도로 없으므로, 호출 측에서 예외 처리가 잘 되어 있는지 확인이 필요합니다.에러 처리를 위해 try-catch 블록 또는 공통 에러 핸들러를 두는 것도 고려해 보세요.
src/pages/dashboard/ui/ticket/TicketListPage.tsx (1)
26-28: 하드코딩된 eventId
현재eventId를 1로 지정하고 있어 추후 확장성에 제약이 있을 수 있습니다. 사용자의 선택이나 URL 파라미터 등을 통해 동적인 eventId를 받을 수 있도록 개선해 보세요.- const eventId = 1; //수정 필요 + // TODO: URL 파라미터나 다른 상태 관리 로직에 따라 eventId를 동적으로 설정 + const eventId = parseInt(params.eventId, 10) || 1;src/widgets/dashboard/ui/TicketItem.tsx (2)
9-20: 티켓 삭제 처리 로직
handleDelete에서 사용자 확인 후 API를 호출하는 구조가 명확합니다. 삭제 성공 시 페이지를 리로드하는 전략은 간단하지만, SPA 특성상 상태만 갱신해도 충분합니다.- window.location.reload(); + // 상태 관리 혹은 부모 컴포넌트에서 티켓 목록을 다시 가져오는 로직으로 대체 + // 예: onDeleteSuccess(ticketId) 같은 콜백을 전달받아 처리
51-60: 삭제 버튼 애니메이션
드래그 동작에 따라 삭제 버튼이 슬라이드되는 방식이 유려합니다. UI 오류 방지를 위해, 작은 화면에서의 터치나 방향성에 대해 예외 케이스가 없는지 QA 단계에서 한번 더 확인해보면 좋겠습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (3)
src/features/ticket/api/ticket.ts(1 hunks)src/pages/dashboard/ui/ticket/TicketListPage.tsx(3 hunks)src/widgets/dashboard/ui/TicketItem.tsx(1 hunks)
🧰 Additional context used
🧬 Code Definitions (3)
src/pages/dashboard/ui/ticket/TicketListPage.tsx (1)
src/features/ticket/api/ticket.ts (1)
readTicket(9-16)
src/widgets/dashboard/ui/TicketItem.tsx (2)
src/pages/dashboard/ui/ticket/TicketListPage.tsx (1)
ReadTicket(11-17)src/features/ticket/api/ticket.ts (1)
deleteTicket(18-23)
src/features/ticket/api/ticket.ts (1)
src/features/ticket/model/ticketCreation.ts (1)
CreateTicketRequest(1-12)
🔇 Additional comments (11)
src/features/ticket/api/ticket.ts (2)
9-16: 티켓 조회 기능 추가
readTicket.getAll메서드를 통해 이벤트 ID에 맞춰 티켓 목록을 가져오는 로직입니다. API 호출 로직이 간단 명료하며, 상태 관리에서 바로 사용하기에 적합해 보입니다.
18-23: 티켓 삭제 기능 추가
deleteTicket.remove함수를 통해 특정 티켓을 삭제하는 흐름이 깔끔하게 잘 구성되어 있습니다. 이후 로직에서 적절한 재렌더링(또는 fetch)만 추가해 주면 충분할 것 같습니다.src/pages/dashboard/ui/ticket/TicketListPage.tsx (4)
8-9: React 훅 및 API 모듈 임포트
useEffect,useState훅과readTicket모듈을 불러오는 변경 사항으로, 의존 관계가 명확합니다.
11-17: 티켓 데이터 인터페이스 정의
ReadTicket인터페이스에 필요한 속성이 잘 정리되어 있습니다. 향후 다른 필드를 추가해도 확장하기 용이한 구조입니다.
72-73: 실제 티켓 목록 렌더링
tickets.length > 0조건으로 분기 처리하여 티켓 목록 혹은 안내 문구를 보여주는 UI 흐름이 적절합니다.
29-44:Details
✅ Verification successful
티켓 목록 가져오기 로직
isSuccess와result필드를 이용하는 부분이 있는데, 실제 API 응답 형식이 이와 일치하는지 확인이 필요합니다. 에러 처리를 잘 하고 있어 안정적입니다만, 서버에서 오는 데이터 구조가 변경될 가능성이 있으면 유연하게 대응할 수 있도록 주의가 필요합니다.
🏁 Script executed:
#!/bin/bash # API 응답에서 isSuccess, result 필드 유무를 검색하여 체크 rg -A 5 "isSuccess" rg -A 5 "result"Length of output: 2626
확인 결과, API 응답의
isSuccess와result필드 사용은 코드베이스 전반에서 일관되게 적용되고 있습니다.
- 검증 내역:
src/pages/dashboard/ui/ticket/TicketListPage.tsx에서 해당 필드를 사용해 티켓 데이터를 정상적으로 처리하고 있음.- 다른 관련 파일들(
src/widgets/event/model/hostChannelList.ts및src/pages/event-manage/ui/HostSelectionPage.tsx)에서도 동일한 필드가 사용됨.현재로서는 API 응답 구조와 맞춰 잘 처리되고 있으나, 서버 데이터 구조가 변경될 경우를 대비해 유연한 대응 로직을 준비하는 것을 권장합니다.
src/widgets/dashboard/ui/TicketItem.tsx (5)
3-6: 필요 모듈 임포트
ReadTicket,motion,useState,deleteTicket를 불러오는 부분입니다. 새로운 기능(드래그, 삭제)에 필요한 라이브러리가 적절하게 추가되었습니다.
8-8: 컴포넌트 시그니처 수정
ticket프로퍼티 타입이ReadTicket으로 변경되었습니다. 전체적인 모듈 연동(생성, 조회, 삭제) 흐름과 일관성을 갖습니다.
22-35: 드래그 가능 영역 구현
framer-motion을 이용해 드래그 기능을 구현한 부분으로, UI/UX에 필요한 이벤트를 깔끔히 담고 있습니다.dragConstraints를 조절해 원하는 범위를 제어할 수 있어 확장성도 좋습니다.
36-38: 티켓 가격 기반 분기 처리
가격이 0이면무료, 아니면일반표시로 분기하는 로직이 간단하고 명확합니다. 추후 다양한 티켓 타입 구분이 필요하면 조건 분기 로직 확장에 대비해주세요.
40-49: 티켓 상세 정보 뷰
남은 티켓 수, 1인당 최대 구매 수량 정보를 적절히 UI에 배치했습니다. 직관적인 정보 전달에 좋아 보입니다.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
src/widgets/dashboard/ui/TicketItem.tsx (3)
8-20: 컴포넌트의 삭제 기능 구현이 잘 되었습니다.티켓 삭제 기능을 위한 핸들러 함수가 잘 구현되었고, 사용자에게 확인 메시지를 보여주는 것은 좋은 UX 관행입니다.
다만, 성공 후 전체 페이지를 리로드하는 방식은 사용자 경험을 저하시킬 수 있습니다. 상태 관리나 콜백을 통해 티켓 목록만 갱신하는 방법을 고려해보세요.
- window.location.reload(); + // 부모 컴포넌트로부터 전달받은 콜백 함수 사용 + onTicketDelete(ticket.ticketId);
23-35: 프레이머 모션을 활용한 드래그 인터랙션 구현이 좋습니다.드래그 기능을 위해 프레이머 모션을 사용한 것은 좋은 선택입니다. 드래그 제약 조건과 이벤트 핸들러가 적절히 구현되어 있습니다.
접근성 향상을 위해 스와이프 동작이 가능하다는 시각적 힌트나 안내를 추가하는 것을 고려해보세요.
51-60: 삭제 버튼 애니메이션 구현이 잘 되었습니다.프레이머 모션을 활용한 삭제 버튼 애니메이션이 잘 구현되었습니다. 드래그 상태에 따라 버튼이 자연스럽게 나타나고 사라지는 효과가 좋습니다.
다만 삭제 작업 중 로딩 상태를 표시하지 않아 사용자가 작업 진행 상황을 알기 어렵습니다. 삭제 중 로딩 표시기를 추가하는 것이 좋을 것 같습니다.
+ const [isDeleting, setIsDeleting] = useState(false); const handleDelete = async () => { const isConfirmed = window.confirm("티켓을 삭제하시겠습니까?"); if (!isConfirmed) return; try { + setIsDeleting(true); await deleteTicket.remove(ticket.ticketId); alert("티켓이 삭제되었습니다."); window.location.reload(); } catch (error) { console.error("티켓 삭제 중 오류 발생:", error); + alert("티켓 삭제에 실패했습니다."); } finally { + setIsDeleting(false); } };버튼에도 로딩 상태를 반영하세요:
<motion.button className="absolute right-0 top-0 bottom-0 bg-red-500 text-white rounded-lg px-4 flex items-center justify-center transition-transform" initial={{ x: '100%' }} animate={{ x: isDragging ? 0 : 'calc(100% + 1px)' }} transition={{ duration: 0 }} onClick={handleDelete} + disabled={isDeleting} > - 삭제 + {isDeleting ? "삭제 중..." : "삭제"} </motion.button>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/widgets/dashboard/ui/TicketItem.tsx(1 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
src/widgets/dashboard/ui/TicketItem.tsx (2)
src/pages/dashboard/ui/ticket/TicketListPage.tsx (1)
ReadTicket(11-17)src/features/ticket/api/ticket.ts (1)
deleteTicket(18-23)
🔇 Additional comments (1)
src/widgets/dashboard/ui/TicketItem.tsx (1)
36-39: 티켓 가격 표시 로직이 깔끔하게 구현되었습니다.가격에 따라 "일반" 또는 "무료" 텍스트를 조건부로 표시하는 로직이 간결하고 명확합니다.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
src/widgets/dashboard/ui/TicketItem.tsx (3)
10-20: 티켓 삭제 기능 구현 확인삭제 기능이 적절하게 구현되었습니다. 사용자 확인을 요청하고, API 호출을 통해 삭제를 수행하며, 오류 처리도 잘 되어 있습니다.
다만, 삭제 후
window.location.reload()를 사용하는 것은 전체 페이지를 새로고침하여 사용자 경험을 저하시킬 수 있습니다.전체 페이지 새로고침 대신 상태 관리를 통해 UI를 업데이트하는 방식으로 개선하는 것을 고려해보세요:
- window.location.reload(); + // 부모 컴포넌트로부터 상태 업데이트 함수를 받아 호출 + onDeleteSuccess(ticket.ticketId);이를 위해서는 컴포넌트 props에
onDeleteSuccess함수를 추가해야 합니다:- const TicketItem = ({ ticket }: { ticket: ReadTicket }) => { + const TicketItem = ({ ticket, onDeleteSuccess }: { ticket: ReadTicket, onDeleteSuccess: (id: number) => void }) => {
22-35: 드래그 인터랙션 구현framer-motion을 활용한 드래그 인터랙션이 잘 구현되어 있습니다. 제약 조건과 이벤트 핸들러를 통해 사용자가 직관적으로 삭제 기능을 사용할 수 있도록 했습니다.
추가적으로 드래그 기능에 대한 시각적 힌트나 사용자 가이드를 제공하면 더 좋을 것 같습니다.
51-60: 삭제 버튼 UI 구현삭제 버튼의 애니메이션과 위치 지정이 잘 되어 있습니다. 사용자가 드래그할 때만 버튼이 나타나도록 하여 UI가 깔끔하게 유지됩니다.
접근성 측면에서 개선할 점이 있습니다:
aria 속성을 추가하여 스크린 리더 사용자에게도 기능을 명확히 전달하는 것이 좋습니다:
<motion.button className="absolute right-0 top-0 bottom-0 bg-red-500 text-white rounded-lg px-4 flex items-center justify-center transition-transform" initial={{ x: '100%' }} animate={{ x: isDragging ? 0 : 'calc(100% + 1px)' }} transition={{ duration: 0 }} onClick={handleDelete} + aria-label="티켓 삭제" + role="button" > 삭제 </motion.button>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/widgets/dashboard/ui/TicketItem.tsx(1 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
src/widgets/dashboard/ui/TicketItem.tsx (2)
src/pages/dashboard/ui/ticket/TicketListPage.tsx (1)
ReadTicket(11-17)src/features/ticket/api/ticket.ts (1)
deleteTicket(18-23)
🔇 Additional comments (3)
src/widgets/dashboard/ui/TicketItem.tsx (3)
3-6: 새로운 기능을 위한 적절한 모듈 임포트필요한 모듈들이 적절하게 임포트되었습니다.
ReadTicket인터페이스를 사용하여 티켓 타입을 명확히 정의하고, framer-motion을 활용한 애니메이션과 삭제 기능 구현을 위한 모듈들이 잘 추가되었습니다.
8-9: 타입 변경 및 상태 관리 추가
TicketType에서ReadTicket으로 타입이 적절하게 변경되었으며, 드래그 상태를 관리하기 위한 state가 추가되었습니다. 이는 사용자 인터페이스의 상호작용성을 향상시키는 좋은 접근입니다.
36-49: 티켓 정보 표시 개선티켓 가격에 따라 "일반" 또는 "무료"로 표시하는 방식이 직관적입니다. 레이아웃과 스타일링도 깔끔하게 적용되었습니다.
티켓 생성 API 연동
(아직 이벤트 id 연결 x)
추후 datepicker 통합 필요
티켓 목록
삭제 버튼
왼쪽으로 슬라이드 시 삭제 버튼 보이도록 구현.

Summary by CodeRabbit
Summary by CodeRabbit
새로운 기능
리팩토링