Skip to content

feat: 티켓 생성 API 연동#80

Merged
hyeeuncho merged 7 commits intodevelopfrom
feat/#77/ticket-api
Mar 29, 2025
Merged

feat: 티켓 생성 API 연동#80
hyeeuncho merged 7 commits intodevelopfrom
feat/#77/ticket-api

Conversation

@hyeeuncho
Copy link
Copy Markdown
Member

@hyeeuncho hyeeuncho commented Mar 25, 2025

티켓 생성 API 연동
(아직 이벤트 id 연결 x)

추후 datepicker 통합 필요

  • 기존의 datepicker는 eventState 관리와 의존성이 있어 사용이 어려워 새로운 컴포넌트를 생성. 향후 기존 이벤트 상태 관리를 분리하고 컴포넌트를 합치는 방향으로 개선하면 좋을 것 같음.

티켓 목록

스크린샷 2025-03-28 오후 5 16 14

삭제 버튼

왼쪽으로 슬라이드 시 삭제 버튼 보이도록 구현.
스크린샷 2025-03-29 오후 3 31 03

Summary by CodeRabbit

Summary by CodeRabbit

  • 새로운 기능

    • 티켓 등록을 위한 비동기 API 기능을 도입하여 티켓 생성 요청을 지원합니다.
    • 날짜 및 시간 선택 기능이 강화되어, 티켓 일정 설정이 보다 직관적으로 개선되었습니다.
    • 티켓 관련 상태 관리를 위한 새로운 컨텍스트와 커스텀 훅이 추가되었습니다.
    • 티켓 목록을 실시간으로 불러오는 기능이 추가되었습니다.
    • 티켓 삭제 기능이 추가되어 사용자 인터페이스에서 티켓을 삭제할 수 있습니다.
    • 티켓 항목의 드래그 앤 드롭 기능이 도입되어 사용자 경험이 향상되었습니다.
  • 리팩토링

    • 티켓 생성 화면의 상태 관리와 이벤트 처리 로직이 통합되어, 티켓 등록 과정이 간소화되고 일관성이 향상되었습니다.

@hyeeuncho hyeeuncho added the 🔧 Feature 기능 구현 label Mar 25, 2025
@hyeeuncho hyeeuncho requested a review from Yejiin21 March 25, 2025 08:16
@hyeeuncho hyeeuncho self-assigned this Mar 25, 2025
@hyeeuncho hyeeuncho linked an issue Mar 25, 2025 that may be closed by this pull request
3 tasks
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 25, 2025

"""

Walkthrough

이 PR은 티켓 생성 기능 관련 전반적인 코드 개선을 포함합니다.
새로운 API 함수인 createTicket이 추가되어 서버에 티켓 생성 요청을 보내며, 티켓 상태 관리를 위한 Context와 커스텀 훅(useTicketState)이 도입되었습니다.
또한, 날짜 및 시간 선택을 위한 TicketDatePicker 컴포넌트가 추가되고, 티켓 생성 페이지에서 상태 관리를 단일 객체로 통합하고 이벤트 핸들러를 개선하여 API 연동을 수행합니다.

Changes

파일 경로 변경 요약
src/.../ticket/api/ticket.ts createTicket 비동기 함수 추가 (axios POST 요청), readTicketdeleteTicket 객체 추가
src/.../ticket/model/TicketContext.tsx, TicketDatePicker.tsx, ticketCreation.ts 티켓 상태 관리: TicketState 인터페이스, TicketProvider 컴포넌트, 커스텀 훅(useTicketState), 날짜 선택 컴포넌트(TicketDatePicker), CreateTicketRequest 인터페이스 추가
src/.../ticket/TicketCreatePage.tsx 티켓 생성 페이지에서 상태를 단일 객체(ticketData)로 통합, 이벤트 핸들러(handleInputChange, handleDateChange) 개선 및 API 호출 기능(handleSaveClick) 추가
src/.../ticket/TicketListPage.tsx ReadTicket 인터페이스 추가, tickets 상태 변수로 API에서 데이터 가져오기
src/.../widgets/dashboard/ui/TicketItem.tsx ticket prop의 타입을 ReadTicket으로 변경, 삭제 기능 추가 및 드래그 가능 UI 구현

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: 응답 데이터 반환
Loading

Possibly related issues

  • [feat] 티켓 생성 API 연동 #77: 이 PR에서 createTicket 함수와 deleteTicket 객체의 도입은 티켓 생성 API 통합과 관련된 문제의 목표를 직접적으로 다룹니다.
    """

🐰 새로운 티켓을 만들고,
날짜를 고르고,
정보를 담아,
API로 보내요.
모두 함께 즐거운 날,
티켓으로 만나요! 🎟️

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

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

CodeRabbit Commands (Invoked using PR comments)

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

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

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

@github-actions
Copy link
Copy Markdown

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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: 인터페이스가 잘 정의되어 있습니다.

티켓 생성에 필요한 모든 속성이 명확하게 정의되어 있으며 각 속성의 타입이 적절합니다.

몇 가지 개선 사항을 고려해 볼 수 있습니다:

  1. 날짜와 시간 형식에 대한 주석을 추가하면 좋을 것 같습니다.
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;
}
  1. 필요한 경우 나중에 확장성을 고려하여 일부 필드를 선택적으로 만들거나 유니온 타입을 사용하여 더 제한적인 타입을 정의할 수 있습니다.
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 컴포넌트가 반응형으로 잘 구현되어 있으며, 날짜 선택기 커스터마이징도 잘 되어 있습니다.

접근성 개선을 위해 몇 가지 수정을 제안합니다:

  1. 라벨과 입력 필드의 연결을 위한 레이블 요소 추가:
- {!isLabel && <span className="text-sm font-medium">시작 날짜</span>}
+ {!isLabel && <label htmlFor="startDate" className="text-sm font-medium">시작 날짜</label>}
  1. 시간 선택 드롭다운에도 레이블 추가:
+ <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="시작 시간"
>
  1. 버튼에 접근성 레이블 추가:
- <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: 중복된 상태 관리에 주의하세요.

eventStateticketData 내의 날짜/시간 필드가 중복으로 관리되고 있습니다. 이런 구조는 두 상태가 동기화되지 않을 위험이 있습니다.

가능하다면 하나의 상태만 사용하거나, 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

📥 Commits

Reviewing files that changed from the base of the PR and between 7e5d0f4 and 1997f8b.

⛔ Files ignored due to path filters (1)
  • yarn.lock is 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 함수에서 동일한 데이터를 두 상태에 모두 업데이트하고 있습니다.

이전 코멘트에서 언급한 대로 상태 관리 방식을 개선하면 이 함수도 단순화할 수 있을 것입니다.

@github-actions
Copy link
Copy Markdown

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/pages/dashboard/ui/ticket/TicketCreatePage.tsx (1)

12-23: ⚠️ Potential issue

이벤트 ID가 하드코딩되어 있습니다.

eventId1로 하드코딩되어 있습니다. 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: 초기 상태값 설정이 불완전합니다.

날짜 관련 초기값에 일관성이 없습니다. startTimeendTime에는 기본값이 설정되어 있지만 startDateendDate는 빈 문자열로 초기화되어 있습니다. 사용자 경험 측면에서 날짜도 기본값을 설정하거나, 모두 빈 값으로 통일하는 것이 좋습니다.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1997f8b and 889b5db.

📒 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 요약에 맞게 잘 구현되었습니다.

Comment on lines +74 to +87
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('티켓 저장에 실패했습니다. 다시 시도해주세요.');
}
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ 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.

Suggested change
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('티켓 저장에 실패했습니다. 다시 시도해주세요.');
}
};

@github-actions
Copy link
Copy Markdown

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 889b5db and b9506e0.

⛔ Files ignored due to path filters (1)
  • yarn.lock is 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

티켓 목록 가져오기 로직
isSuccessresult 필드를 이용하는 부분이 있는데, 실제 API 응답 형식이 이와 일치하는지 확인이 필요합니다. 에러 처리를 잘 하고 있어 안정적입니다만, 서버에서 오는 데이터 구조가 변경될 가능성이 있으면 유연하게 대응할 수 있도록 주의가 필요합니다.


🏁 Script executed:

#!/bin/bash
# API 응답에서 isSuccess, result 필드 유무를 검색하여 체크
rg -A 5 "isSuccess" 
rg -A 5 "result"

Length of output: 2626


확인 결과, API 응답의 isSuccessresult 필드 사용은 코드베이스 전반에서 일관되게 적용되고 있습니다.

  • 검증 내역:
    • src/pages/dashboard/ui/ticket/TicketListPage.tsx에서 해당 필드를 사용해 티켓 데이터를 정상적으로 처리하고 있음.
    • 다른 관련 파일들(src/widgets/event/model/hostChannelList.tssrc/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에 배치했습니다. 직관적인 정보 전달에 좋아 보입니다.

@github-actions
Copy link
Copy Markdown

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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

📥 Commits

Reviewing files that changed from the base of the PR and between b9506e0 and 647611f.

📒 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: 티켓 가격 표시 로직이 깔끔하게 구현되었습니다.

가격에 따라 "일반" 또는 "무료" 텍스트를 조건부로 표시하는 로직이 간결하고 명확합니다.

Copy link
Copy Markdown
Contributor

@Yejiin21 Yejiin21 left a comment

Choose a reason for hiding this comment

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

수고하셨습니다!

@github-actions
Copy link
Copy Markdown

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 647611f and 69c40db.

📒 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: 티켓 정보 표시 개선

티켓 가격에 따라 "일반" 또는 "무료"로 표시하는 방식이 직관적입니다. 레이아웃과 스타일링도 깔끔하게 적용되었습니다.

@hyeeuncho hyeeuncho merged commit 42e1dbf into develop Mar 29, 2025
2 checks passed
@hyeeuncho hyeeuncho deleted the feat/#77/ticket-api branch March 29, 2025 06:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔧 Feature 기능 구현

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 티켓 생성 API 연동

2 participants