Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/features/dashboard/model/email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,5 @@ export interface ReadEmailResponse {
content: string;
recipients: string[];
reservationDate: string;
reservationTime: string;
targetName: string
}
17 changes: 6 additions & 11 deletions src/features/dashboard/model/store/TicketOptionStore.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,28 @@
import { create } from "zustand";

export interface TOption {
type: string;
optionName: string;
required: boolean;
choices: string[];
}

interface TicketOptionState {
currentPage: number;
setCurrentPage: (page: number) => void;

selectedOptions: { [index: number]: { [key: string]: string | string[] } };
setOption: (index: number, optionName: string, value: string | string[]) => void;
selectedOptions: { [page: number]: { [optionId: number]: string | number | number[] } };
setOption: (page: number, optionId: number, value: string | number | number[]) => void;
resetOptions: () => void;
}

export const useTicketOptionStore = create<TicketOptionState>((set) => ({
currentPage: 1,
setCurrentPage: (page: number) => set({ currentPage: page }),

selectedOptions: {},
setOption: (index, optionName, value) => {
setOption: (index, optionId, value) => {
set((state) => {
const updatedSelectedOptions = { ...state.selectedOptions };
if (!updatedSelectedOptions[index]) {
updatedSelectedOptions[index] = {};
}
updatedSelectedOptions[index][optionName] = value;
updatedSelectedOptions[index][optionId] = value;
return { selectedOptions: updatedSelectedOptions };
});
},
resetOptions: () => set({ selectedOptions: {}, currentPage: 1 }),
}));
96 changes: 45 additions & 51 deletions src/features/payment/ui/TicketOption.tsx
Original file line number Diff line number Diff line change
@@ -1,60 +1,75 @@
import { TOption } from '../../dashboard/model/store/TicketOptionStore';
import { useTicketOptionStore } from '../../dashboard/model/store/TicketOptionStore';
import Checkbox from '../../../../design-system/ui/Checkbox';
import { useTicketOptionStore } from "../../dashboard/model/store/TicketOptionStore";
import Checkbox from "../../../../design-system/ui/Checkbox";
import { TicketOptionResponse } from "../../ticket/model/ticketInformation";

interface TicketOptionProps {
options: TOption[];
options: TicketOptionResponse[];
}

const TicketOption = ({ options }: TicketOptionProps) => {
const { currentPage, selectedOptions, setOption } = useTicketOptionStore();
const currentSelectedOptions = selectedOptions[currentPage] || {};

const handleChange = (type: string, optionName: string, value: string) => {
if (type === 'text' || type === 'single') {
setOption(currentPage, optionName, value);
} else if (type === 'multiple') {
const prevValues = (currentSelectedOptions[optionName] as string[]) || [];
const newValues = prevValues.includes(value) ? prevValues.filter(v => v !== value) : [...prevValues, value];
setOption(currentPage, optionName, newValues);
const handleChange = (
type: "text" | "single" | "multiple",
optionId: number,
value: string | number
) => {
if (type === "text") {
setOption(currentPage, optionId, value as string);
} else if (type === "single") {
setOption(currentPage, optionId, value as number);
} else if (type === "multiple") {
const prevValues = (currentSelectedOptions[optionId] as number[]) || [];
const newValues = prevValues.includes(value as number)
? prevValues.filter((v) => v !== value)
: [...prevValues, value as number];
setOption(currentPage, optionId, newValues);
}
console.log(selectedOptions)
};

return (
<div className="text-sm md:text-base font-semibold">
{options.map((option, index) => (
<div key={index} className="mt-4">
{options.map((option) => (
<div key={option.id} className="mt-4">
<p>
{option.optionName} {option.required && <span className="text-red-500">*</span>}
{option.name}
{/* {option.isMandatory && <span className="text-red-500">*</span>} */}
</p>

{option.type === 'text' && (
{option.type === "TEXT" && (
<input
type="text"
className="w-full p-2 border rounded-md mt-2"
placeholder="입력해주세요"
value={currentSelectedOptions[option.optionName] || ''}
onChange={e => handleChange('text', option.optionName, e.target.value)}
value={(currentSelectedOptions[option.id] as string) || ""}
onChange={(e) =>
handleChange("text", option.id, e.target.value)
}
/>
)}

{option.type === 'single' &&
option.choices.map(choice => (
{option.type === "SINGLE" &&
option.choices.map((choice) => (
<Checkbox
key={choice}
label={choice}
checked={currentSelectedOptions[option.optionName] === choice}
onChange={() => handleChange('single', option.optionName, choice)}
key={choice.id}
label={choice.name}
checked={currentSelectedOptions[option.id] === choice.id}
onChange={() => handleChange("single", option.id, choice.id)}
className="block mt-2"
/>
))}

{option.type === 'multiple' &&
option.choices.map(choice => (
{option.type === "MULTIPLE" &&
option.choices.map((choice) => (
<Checkbox
key={choice}
label={choice}
checked={(currentSelectedOptions[option.optionName] as string[])?.includes(choice)}
onChange={() => handleChange('multiple', option.optionName, choice)}
key={choice.id}
label={choice.name}
checked={(
(currentSelectedOptions[option.id] as number[]) || []
).includes(choice.id)}
onChange={() => handleChange("multiple", option.id, choice.id)}
className="block mt-2"
/>
))}
Expand All @@ -63,26 +78,5 @@ const TicketOption = ({ options }: TicketOptionProps) => {
</div>
);
};
export default TicketOption;

// 임의 데이터
export const options: TOption[] = [
{
type: 'text',
optionName: '성함을 알려주세요.',
required: true,
choices: [''],
},
{
type: 'single',
optionName: '티셔츠 사이즈 선택해주세요.',
required: true,
choices: ['S', 'M', 'L', 'XL'],
},
{
type: 'multiple',
optionName: '선택해주세요.',
required: false,
choices: ['1', '2', '3', '4'],
},
];
export default TicketOption;
4 changes: 2 additions & 2 deletions src/features/ticket/api/order.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { axiosClient } from '../../../shared/types/api/http-client';
import { OrderTicketRequest } from '../model/order';
import { axiosClient } from "../../../shared/types/api/http-client"
import { OrderTicketRequest } from "../model/orderInformation";

export const readTicket = {
// 주문 티켓 전체 조회
Expand Down
18 changes: 14 additions & 4 deletions src/features/ticket/api/ticket.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { axiosClient } from '../../../shared/types/api/http-client';
import { CreateTicketRequest, ReadTicketResponse } from '../model/ticket';
import { axiosClient } from "../../../shared/types/api/http-client";
import { CreateTicketRequest, ReadTicketResponse, TicketOptionAnswerRequest, TicketOptionResponse } from "../model/ticketInformation";

export const createTicket = async (data: CreateTicketRequest) => {
const response = await axiosClient.post('/tickets', data);
Expand All @@ -14,6 +14,16 @@ export const readTicket = async (eventId: number): Promise<{ isSuccess: boolean;
};

export const deleteTicket = async (ticketId: number) => {
const response = await axiosClient.delete(`/tickets/${ticketId}`);
return response.data;
const response = await axiosClient.delete(`/tickets/${ticketId}`);
return response.data;
}

export const readTicketOptions = async (ticketId: number): Promise<{ isSuccess: boolean; result: TicketOptionResponse[] }> => {
const response = await axiosClient.get(`/ticket-options/tickets/${ticketId}`);
return response.data;
};

export const createTicketOptionAnswers = async (answers: TicketOptionAnswerRequest) => {
const response = await axiosClient.post("/ticket-option-answers", answers);
return response.data;
};
43 changes: 43 additions & 0 deletions src/features/ticket/hooks/useOrderHook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useMutation, useQuery } from '@tanstack/react-query';
import { cancelTickets, orderTickets, readTicket } from '../api/order';
import { OrderTicketRequest } from '../model/orderInformation';

// 주문 전체 조회
export const useTicketOrders = (page: number = 0, size: number = 10) => {
return useQuery({
queryKey: ['ticketOrders', page, size],
queryFn: () => readTicket.getAll(page, size),
});
};

// 주문 상세 조회
export const useTicketOrderDetail = (ticketId: number, eventId: number) => {
return useQuery({
queryKey: ['ticketOrderDetail', ticketId, eventId],
queryFn: () => readTicket.getDetail(ticketId, eventId),
enabled: !!ticketId && !!eventId,
});
};

// 주문 취소
export const useCancelTicket = () => {
return useMutation({
mutationFn: (orderId: number) => cancelTickets(orderId),
onSuccess: () => {
alert('티켓이 성공적으로 취소되었습니다.');
},
onError: () => {
alert('티켓 취소에 실패하였습니다.');
},
});
};

// 티켓 구매
export const useOrderTicket = () => {
return useMutation({
mutationFn: (data: OrderTicketRequest) => orderTickets(data),
onError: () => {
alert("티켓 구매 중 오류가 발생했습니다.");
},
});
};
30 changes: 25 additions & 5 deletions src/features/ticket/hooks/useTicketHook.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useMutation, useQuery } from '@tanstack/react-query';
import { createTicket, deleteTicket, readTicket } from '../api/ticket';
import { CreateTicketRequest, ReadTicketResponse } from '../model/ticket';
import { ApiResponse } from '../../../shared/types/api/apiResponse';
import { AxiosError } from 'axios';
import { useMutation, useQuery } from "@tanstack/react-query";
import { createTicket, createTicketOptionAnswers, deleteTicket, readTicket, readTicketOptions } from "../api/ticket";
import { CreateTicketRequest, ReadTicketResponse, TicketOptionAnswerRequest, TicketOptionResponse, TicketResponse } from "../model/ticketInformation";
import { ApiResponse } from "../../../shared/types/api/apiResponse";
import { AxiosError } from "axios";

export const useTickets = (eventId: number) => {
return useQuery<{ isSuccess: boolean; result: ReadTicketResponse[] }>({
Expand Down Expand Up @@ -37,3 +37,23 @@ export const useDeleteTicket = () => {
},
});
};

export const useTicketOptions = (ticketId: number) => {
return useQuery<{ isSuccess: boolean; result: TicketOptionResponse[] }>({
queryKey: ['ticketOptions', ticketId],
queryFn: () => readTicketOptions(ticketId),
enabled: !!ticketId,
});
};

export const useCreateTicketOptionAnswers = () => {
return useMutation<TicketResponse,Error,TicketOptionAnswerRequest>({
mutationFn: createTicketOptionAnswers,
onSuccess: () => {
console.log("티켓 옵션 응답 전송 성공");
},
onError: () => {
alert("티켓 옵션 응답 전송 중 오류가 발생했습니다.");
},
});
};
19 changes: 19 additions & 0 deletions src/features/ticket/model/orderInformation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export interface OrderTicketRequest {
ticketId: number;
eventId: number;
ticketCnt: number;
}
export interface TicketConfirm {
id: number;
title: string;
startDate: string;
ticketName: string;
hostChannelName: string;
hostChannelDescription: string;
organizerEmail: string;
organizerPhoneNumber: string;
eventAddress: string;
locationLat: number;
locationLng: number;
orderStatus: "COMPLETED" | "PENDING" | "CANCELLED";
};
47 changes: 47 additions & 0 deletions src/features/ticket/model/ticketInformation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
export interface CreateTicketRequest {
eventId: number;
ticketType: string;
ticketName: string;
ticketDescription: string;
ticketPrice: number;
availableQuantity: number;
startDate: string;
endDate: string;
startTime: string;
endTime: string;
}

export interface TicketResponse {
isSuccess: boolean;
code: string;
message: string;
result: string; // "ticketId: 2"
}

export interface ReadTicketResponse {
ticketId: number;
ticketName: string;
ticketDescription: string;
ticketPrice: number;
availableQuantity: number;
}

export interface TicketOptionChoice {
id: number;
name: string;
}
export interface TicketOptionResponse {
id: number;
name: string;
description: string;
type: 'SINGLE' | 'MULTIPLE' | 'TEXT';
isMandatory: boolean;
choices: TicketOptionChoice[];
}

// 티켓 옵션 응답
export interface TicketOptionAnswerRequest {
ticketOptionId: number;
answerText?: string;
ticketOptionChoiceId?: number;
}
Loading