-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 티켓 생성 API 연동 #80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
1997f8b
889b5db
3029187
7a03428
b9506e0
647611f
69c40db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import { axiosClient } from "../../../shared/types/api/http-client"; | ||
| import { CreateTicketRequest } from "../model/ticketCreation"; | ||
|
|
||
| export const createTicket = async(data: CreateTicketRequest) => { | ||
| const response = await axiosClient.post('/tickets', data); | ||
| return response.data; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| import { createContext, ReactNode, useContext, useState } from 'react'; | ||
| import { CreateTicketRequest } from './ticketCreation'; | ||
|
|
||
| export interface TicketState { | ||
| ticketState: CreateTicketRequest; | ||
| setTicketState: React.Dispatch<React.SetStateAction<CreateTicketRequest>>; | ||
| setTicketChannelId: (ticketChannelId: number) => void; | ||
| } | ||
|
|
||
| const TicketContext = createContext<TicketState | undefined>(undefined); | ||
|
|
||
| export const TicketProvider = ({ children }: { children: ReactNode }) => { | ||
| const [ticketState, setTicketState] = useState<CreateTicketRequest>({ | ||
| eventId: 0, | ||
| ticketType: '', | ||
| ticketName: '', | ||
| ticketDescription: '', | ||
| ticketPrice: 0, | ||
| availableQuantity: 0, | ||
| startDate: '', | ||
| endDate: '', | ||
| startTime: '06:00', | ||
| endTime: '23:00', | ||
| }); | ||
|
|
||
| const setTicketChannelId = (ticketChannelId: number) => { | ||
| setTicketState(prev => ({ ...prev, ticketChannelId })); | ||
| }; | ||
|
|
||
| return ( | ||
| <TicketContext.Provider value={{ ticketState, setTicketState, setTicketChannelId }}> | ||
| {children} | ||
| </TicketContext.Provider> | ||
| ); | ||
| }; | ||
|
|
||
| export const useTicketState = () => { | ||
| const context = useContext(TicketContext); | ||
| if (!context) { | ||
| throw new Error('useTicketState must be used within a TicketProvider'); | ||
| } | ||
| return context; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| import { useEffect, useState } from 'react'; | ||
| import DatePicker from 'react-datepicker'; | ||
| import { ko } from 'date-fns/locale'; | ||
| import 'react-datepicker/dist/react-datepicker.css'; | ||
| import { TicketState } from '../model/TicketContext'; | ||
|
|
||
| interface DatePickerProps { | ||
| className?: string; | ||
| ticketState?: TicketState['ticketState']; | ||
| setTicketState?: React.Dispatch<React.SetStateAction<TicketState['ticketState']>>; | ||
| isLabel?: boolean; | ||
| onDateChange: (dates: { startDate: string; endDate: string; startTime: string; endTime: string }) => void; | ||
| } | ||
|
|
||
| const TicketDatePicker = ({ className, ticketState, setTicketState, isLabel = false, onDateChange }: DatePickerProps) => { | ||
| const [startDate, setStartDate] = useState<Date | null>( | ||
| ticketState?.startDate ? new Date(ticketState.startDate) : new Date() | ||
| ); | ||
| const [endDate, setEndDate] = useState<Date | null>(ticketState?.endDate ? new Date(ticketState.endDate) : new Date()); | ||
| const [startTime, setStartTime] = useState<string>(ticketState?.startTime || '06:00'); | ||
| const [endTime, setEndTime] = useState<string>(ticketState?.endTime || '23:00'); | ||
|
|
||
| const generateTimeOptions = () => { | ||
| const options = []; | ||
| for (let i = 0; i < 24; i++) { | ||
| for (let j = 0; j < 4; j++) { | ||
| const hour = i.toString().padStart(2, '0'); | ||
| const minute = (j * 15).toString().padStart(2, '0'); | ||
| options.push(`${hour}:${minute}`); | ||
| } | ||
| } | ||
| return options; | ||
| }; | ||
hyeeuncho marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| const formatDate = (date: Date | null) => { | ||
| if (!date) return ''; | ||
| const year = date.getFullYear(); | ||
| const month = (date.getMonth() + 1).toString().padStart(2, '0'); | ||
| const day = date.getDate().toString().padStart(2, '0'); | ||
| return `${year}-${month}-${day}`; // yyyy-mm-dd 형태로 포맷팅 | ||
| }; | ||
|
|
||
| const timeOptions = generateTimeOptions(); | ||
|
|
||
| useEffect(() => { | ||
| 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]); | ||
|
|
||
| return ( | ||
| <div className={`flex flex-col w-full ${className}`}> | ||
| <div className="flex flex-wrap lg:flex-nowrap items-center justify-between gap-2"> | ||
| <div className="flex flex-col w-full sm:w-auto gap-2"> | ||
| {!isLabel && <span className="text-sm font-medium">시작 날짜</span>} | ||
| <div className="flex gap-1"> | ||
| <DatePicker | ||
| id="startDate" | ||
| selected={startDate} | ||
| onChange={(date: Date | null) => setStartDate(date)} | ||
| locale={ko} | ||
| dateFormat="MM월 dd일" | ||
| className="w-20 h-9 md:w-24 md:h-10 border border-placeholderText text-sm md:text-md rounded-[5px] p-2" | ||
| renderCustomHeader={({ | ||
| date, | ||
| decreaseMonth, | ||
| increaseMonth, | ||
| prevMonthButtonDisabled, | ||
| nextMonthButtonDisabled, | ||
| }) => ( | ||
| <div className="flex justify-center gap-4"> | ||
| <button onClick={decreaseMonth} disabled={prevMonthButtonDisabled} className="mb-1"> | ||
| < | ||
| </button> | ||
| <span> | ||
| {date.getFullYear()}년 {date.getMonth() + 1}월 | ||
| </span> | ||
| <button onClick={increaseMonth} disabled={nextMonthButtonDisabled} className="mb-1"> | ||
| > | ||
| </button> | ||
| </div> | ||
| )} | ||
| /> | ||
| <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" | ||
| > | ||
| {timeOptions.map(time => ( | ||
| <option key={time} value={time}> | ||
| {time} | ||
| </option> | ||
| ))} | ||
| </select> | ||
| </div> | ||
| </div> | ||
|
|
||
| {isLabel && <span className="text-2xl hidden lg:inline">></span>} | ||
|
|
||
| <div className="flex flex-col w-full sm:w-auto gap-2"> | ||
| {!isLabel && <span className="text-sm font-medium">종료 날짜</span>} | ||
| <div className="flex gap-1"> | ||
| <DatePicker | ||
| id="endDate" | ||
| selected={endDate} | ||
| onChange={(date: Date | null) => setEndDate(date)} | ||
| locale={ko} | ||
| dateFormat="MM월 dd일" | ||
| className="w-20 h-9 md:w-24 md:h-10 border border-placeholderText text-sm md:text-md rounded-[5px] p-2" | ||
| renderCustomHeader={({ | ||
| date, | ||
| decreaseMonth, | ||
| increaseMonth, | ||
| prevMonthButtonDisabled, | ||
| nextMonthButtonDisabled, | ||
| }) => ( | ||
| <div className="flex justify-center gap-4"> | ||
| <button onClick={decreaseMonth} disabled={prevMonthButtonDisabled} className="mb-1"> | ||
| < | ||
| </button> | ||
| <span> | ||
| {date.getFullYear()}년 {date.getMonth() + 1}월 | ||
| </span> | ||
| <button onClick={increaseMonth} disabled={nextMonthButtonDisabled} className="mb-1"> | ||
| > | ||
| </button> | ||
| </div> | ||
| )} | ||
| /> | ||
| <select | ||
| id="endTime" | ||
| value={endTime} | ||
| onChange={e => setEndTime(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" | ||
| > | ||
| {timeOptions.map(time => ( | ||
| <option key={time} value={time}> | ||
| {time} | ||
| </option> | ||
| ))} | ||
| </select> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default TicketDatePicker; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| export interface CreateTicketRequest { | ||
| eventId: number; | ||
| ticketType: string; | ||
| ticketName: string; | ||
| ticketDescription: string; | ||
| ticketPrice: number; | ||
| availableQuantity: number; | ||
| startDate: string; | ||
| endDate: string; | ||
| startTime: string; | ||
| endTime: string; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,24 +4,62 @@ import { TwoOptions } from '../../../../../design-system/stories/ChoiceChip.stor | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import ChoiceChip from '../../../../../design-system/ui/ChoiceChip'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import DefaultTextField from '../../../../../design-system/ui/textFields/DefaultTextField'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import Button from '../../../../../design-system/ui/Button'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import EventDatePicker from '../../../../features/event-manage/event-create/ui/DatePicker'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import TicketDatePicker from '../../../../features/ticket/model/TicketDatePicker'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { createTicket } from '../../../../features/ticket/api/ticket'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { CreateTicketRequest } from '../../../../features/ticket/model/ticketCreation'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const TicketCreatePage = () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [price, setPrice] = useState<number>(0); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [quantity, setQuantity] = useState<number>(0); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [ticketData, setTicketData] = useState<CreateTicketRequest>({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| eventId: 1, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ticketType: 'FIRST_COME', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ticketName: '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ticketDescription: '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ticketPrice: 0, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| availableQuantity: 0, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| startDate: '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| endDate: '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| startTime: '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| endTime: '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handlePriceChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const value = Number(e.target.value); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setPrice(Number.isNaN(value) ? 0 : value); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleTicketTypeChange = (type: string) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mappedType: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (type === '선착순') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mappedType = 'FIRST_COME'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mappedType = 'SELECTION'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setTicketData((prev) => ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...prev, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ticketType: mappedType, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleQuantityChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const value = Number(e.target.value); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setQuantity(Number.isNaN(value) ? 0 : value); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 필드값 업데이트 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleInputChange = (field: keyof CreateTicketRequest) => (e: React.ChangeEvent<HTMLInputElement>) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const value = e.target.value; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ((field === 'ticketPrice' || field === 'availableQuantity') && Number(value) < 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setTicketData((prev) => ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...prev, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [field]: field === 'ticketPrice' || field === 'availableQuantity' ? Number(value) : value, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
hyeeuncho marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const sum = price * quantity; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 시간 업데이트 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleDateChange = (dates: { startDate: string; endDate: string; startTime: string; endTime: string }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setTicketData((prevState) => ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...prevState, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| startDate: dates.startDate, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| endDate: dates.endDate, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| startTime: dates.startTime, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| endTime: dates.endTime, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 예상 수익 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const sum = ticketData.ticketPrice * ticketData.availableQuantity; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const formatNumber = (num: number): string => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (num >= 1000000) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return num / 1000000 + 'M'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -32,6 +70,22 @@ const TicketCreatePage = () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return num.toString(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // API 호출 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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('티켓 저장에 실패했습니다. 다시 시도해주세요.'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
hyeeuncho marked this conversation as resolved.
Show resolved
Hide resolved
Comment on lines
+74
to
+87
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 폼 유효성 검사가 불완전합니다.
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
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <DashboardLayout centerContent="WOOACON 2024"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className=" flex flex-col gap-3 md:gap-5 px-7 py-5"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -45,7 +99,7 @@ const TicketCreatePage = () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="w-32 md:w-40 my-1"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <p className="font-semibold px-1 mb-1 text-gray-700">티켓 종류</p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <ChoiceChip {...TwoOptions.args} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <ChoiceChip {...TwoOptions.args} onSelect={handleTicketTypeChange} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <p className="block px-1 mb-1 text-placeholderText text-11 md:text-13"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 참가자가 선착순으로 발행된 티켓을 구매합니다. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -58,6 +112,7 @@ const TicketCreatePage = () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| label="티켓(입장권) 이름" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| detail="티켓을 잘 나타낼 수 있는 이름을 써보세요.(무료 입장권, VIP 입장권,얼리버드)" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="h-12" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onChange={handleInputChange('ticketName')} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {/*티켓 설명 입력란*/} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -67,14 +122,15 @@ const TicketCreatePage = () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| label="티켓 설명" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| detail="티켓에 대한 상세한 설명을 해주세요." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="h-12" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onChange={handleInputChange('ticketDescription')} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {/*가격 계산 란*/} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex items-center gap-5"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <DefaultTextField label="1개당 가격" className="h-8 md:h-9" onChange={handlePriceChange} placeholder="0" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <DefaultTextField label="1개당 가격" className="h-8 md:h-9" onChange={handleInputChange('ticketPrice')} placeholder="0" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <p className="text-gray-700 text-2xl">X</p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <DefaultTextField label="수량" className="h-8 md:h-9" onChange={handleQuantityChange} placeholder="1" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <DefaultTextField label="수량" className="h-8 md:h-9" onChange={handleInputChange('availableQuantity')} placeholder="1" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <p className="text-gray-700 text-2xl">=</p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <p className="whitespace-nowrap text-gray-700 font-semibold text-15 md:text-base">예상 수익</p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -85,16 +141,18 @@ const TicketCreatePage = () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {/*캘린더가 들어갈 자리*/} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex flex-col gap-2"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <p className="px-1 text-gray-700 font-semibold">판매 기간</p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <EventDatePicker isLabel={true} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <TicketDatePicker isLabel={true} ticketState={ticketData} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setTicketState={setTicketData} onDateChange={handleDateChange} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex-grow"></div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="w-full "> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Button label="저장하기" onClick={() => console.log('post 요청')} className="w-full h-12 rounded-full" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Button label="저장하기" onClick={handleSaveClick} className="w-full h-12 rounded-full" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </DashboardLayout> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default TicketCreatePage; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default TicketCreatePage; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.