From 70e8c4e13f5d84ef181913eef231f0388571d255 Mon Sep 17 00:00:00 2001 From: mii2026 Date: Sun, 22 Dec 2024 17:29:22 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[feat]=20=EC=A2=8C=EC=84=9D=20=EC=84=A0?= =?UTF-8?q?=EC=A0=95=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ticketping/src/App.js | 8 ++-- ticketping/src/pages/seat/Seat.js | 64 +++++++++++++++++++++++++ ticketping/src/pages/seat/SeatLayout.js | 59 +++++++++++++++++++++++ ticketping/src/style/Seat.css | 62 ++++++++++++++++++++++++ ticketping/src/style/SeatLayout.css | 54 +++++++++++++++++++++ 5 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 ticketping/src/pages/seat/Seat.js create mode 100644 ticketping/src/pages/seat/SeatLayout.js create mode 100644 ticketping/src/style/Seat.css create mode 100644 ticketping/src/style/SeatLayout.css diff --git a/ticketping/src/App.js b/ticketping/src/App.js index 1aebc8a..4c5d1a9 100644 --- a/ticketping/src/App.js +++ b/ticketping/src/App.js @@ -3,12 +3,13 @@ import { Routes, Route } from 'react-router-dom' import LoginRequiredRoute from './utils/LoginRequiredRoute'; import Main from './pages/Main'; import PerformanceDetail from './pages/PerformanceDetail' +import SelectSchedule from './pages/SelectSchedule'; +import Seat from './pages/seat/Seat' import AppLayout from './component/AppLayout'; import Login from './pages/Login'; import VerificationInfo from './pages/Join'; import MyPage from './pages/MyPage'; import NotFound from './pages/NotFound'; -import SelectSchedule from './pages/SelectSchedule'; function App() { return ( @@ -16,10 +17,11 @@ function App() { }> }> - } /> + } /> + } /> + }> }> }> - } /> }> diff --git a/ticketping/src/pages/seat/Seat.js b/ticketping/src/pages/seat/Seat.js new file mode 100644 index 0000000..1252123 --- /dev/null +++ b/ticketping/src/pages/seat/Seat.js @@ -0,0 +1,64 @@ +import React, { useState } from 'react'; +import SeatLayout from './SeatLayout'; +import '../../style/Seat.css'; + +function Seat() { + const rows = 5; + const columns = 10; + const performanceName = "햄릿"; + + const grades = ['S', 'A', 'B', 'B', 'B']; + + const seats = Array.from({ length: rows * columns }, (_, index) => ({ + grade: grades[Math.floor(index / columns)], + price: 10000, + reserved: index % 7 === 0, + })); + + const [selectedSeat, setSelectedSeat] = useState(null); + + const handleSeatSelect = (seatId) => { + if (selectedSeat === seatId) { + setSelectedSeat(null); + } else { + setSelectedSeat(seatId); + } + }; + + return ( +
+
+ +
+
+
+

{performanceName}

+ {selectedSeat !== null ? ( +
+

좌석: {Math.floor(selectedSeat / columns) + 1}행 {selectedSeat % columns + 1}열

+

등급: {seats[selectedSeat].grade}

+

가격: {seats[selectedSeat].price}원

+
+ ) : ( +

선택된 좌석이 없습니다.

+ )} +
+ +
+
+ ); +} + +export default Seat; diff --git a/ticketping/src/pages/seat/SeatLayout.js b/ticketping/src/pages/seat/SeatLayout.js new file mode 100644 index 0000000..bbe871b --- /dev/null +++ b/ticketping/src/pages/seat/SeatLayout.js @@ -0,0 +1,59 @@ +import React from 'react'; +import '../../style/SeatLayout.css'; + +function SeatLayout({ rows, columns, grades, seats, selectedSeat, onSeatSelect }) { + + const handleSeatClick = (seatId) => { + if (!seats[seatId].reserved) { + onSeatSelect(seatId); + } + }; + + const seatLayout = []; + + // 공연장 추가 + seatLayout.push( +
+ 공연장 +
+ ); + + for (let i = 0; i < rows; i++) { + const row = []; + const grade = seats[i * columns].grade; // 수정: 올바른 grade를 참조 + row.push( +
+ {grades[i]} +
+ ); + + for (let j = 0; j < columns; j++) { + const seatId = i * columns + j; + const seat = seats[seatId]; + const isReserved = seat.reserved; + const isSelected = seatId === selectedSeat; + + row.push( +
handleSeatClick(seatId)} + title={`등급: ${seat.grade}, 가격: ${seat.price}`} + > + {i + 1}-{j + 1} +
+ ); + } + seatLayout.push( +
+ {row} +
+ ); + } + + return
{seatLayout}
; +} + +export default SeatLayout; diff --git a/ticketping/src/style/Seat.css b/ticketping/src/style/Seat.css new file mode 100644 index 0000000..bb95875 --- /dev/null +++ b/ticketping/src/style/Seat.css @@ -0,0 +1,62 @@ +.seat-page { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin: 20px; +} + +.seat-layout-container { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 550px; +} + +.page-detail { + display: flex; + flex-direction: column; + align-items: flex-start; + margin-left: 50px; + width: 300px; + height: 550px; + min-width: 55vb; + position: relative; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.order-detail { + width: 300px; + height: 400px; + margin: 20px; + padding: 20px; + border-radius: 8px; +} + +.booking-button { + position: absolute; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + height: 50px; + width: 80%; + background-color: #4796eb; + color: white; + border: none; + cursor: pointer; + font-size: 1.1rem; + transition: background-color 0.3s; +} + +.booking-button:hover { + background-color: #4a1cef; + cursor: not-allowed; + opacity: 1; +} + +.booking-button:disabled { + background-color: #d0d0d0; + cursor: not-allowed; + opacity: 1; +} + diff --git a/ticketping/src/style/SeatLayout.css b/ticketping/src/style/SeatLayout.css new file mode 100644 index 0000000..182dcbd --- /dev/null +++ b/ticketping/src/style/SeatLayout.css @@ -0,0 +1,54 @@ +.stage { + width: 100%; + text-align: center; + background-color: #9c9b9b; + color: white; + font-size: 1.2rem; + padding: 10px 0; + margin-bottom: 20px; +} + +.seat-layout-wrapper { + display: flex; + flex-direction: column; + gap: 10px; +} + +.seat-row { + display: flex; + align-items: center; + gap: 5px; + width: 100%; +} + +.seat { + width: 50px; + height: 50px; + background-color: #e0e0e0; + border: 1px solid #b0b0b0; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + cursor: pointer; +} + +.seat.reserved { + background-color: #7e7b7b; + cursor: not-allowed; +} + +.seat.selected { + background-color: #4caf50; +} + +.seat:hover:not(.reserved):not(.selected) { + background-color: #c0c0c0; +} + +.seat-grade { + width: 50px; + text-align: center; + font-weight: bold; + color: #333; +} From 9880d9604f97f44270bffafbfde029c0c3b426f3 Mon Sep 17 00:00:00 2001 From: mii2026 Date: Wed, 25 Dec 2024 10:28:36 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[feat]=20=EC=A2=8C=EC=84=9D=20=EC=84=A0?= =?UTF-8?q?=EC=A0=90=EC=B0=BD=20=EB=B0=B1=EC=97=94=EB=93=9C=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ticketping/src/App.js | 2 +- ticketping/src/pages/SelectSchedule.js | 6 +- ticketping/src/pages/seat/Seat.js | 124 ++++++++++++++++++++---- ticketping/src/pages/seat/SeatLayout.js | 25 +++-- ticketping/src/style/Seat.css | 2 +- 5 files changed, 120 insertions(+), 39 deletions(-) diff --git a/ticketping/src/App.js b/ticketping/src/App.js index 4c5d1a9..3189afe 100644 --- a/ticketping/src/App.js +++ b/ticketping/src/App.js @@ -19,7 +19,7 @@ function App() { }> } /> } /> - }> + }> }> }> }> diff --git a/ticketping/src/pages/SelectSchedule.js b/ticketping/src/pages/SelectSchedule.js index 835bc66..cf63f98 100644 --- a/ticketping/src/pages/SelectSchedule.js +++ b/ticketping/src/pages/SelectSchedule.js @@ -29,7 +29,7 @@ export default function SelectSchedule() { }, [id, headers]); const disabledDate = (current) => { - const hasSchedule = schedules.some(schedule => schedule.startTime.startsWith(current.format('YYYY-MM-DD'))); + const hasSchedule = schedules.some(schedule => schedule.startDate.startsWith(current.format('YYYY-MM-DD'))); return !hasSchedule; }; @@ -39,7 +39,7 @@ export default function SelectSchedule() { const daysWithSchedules = schedules .filter(schedule => { - const scheduleDate = dayjs(schedule.startTime); + const scheduleDate = dayjs(schedule.startDate); return ( scheduleDate.year() === year && scheduleDate.month() === month @@ -83,7 +83,7 @@ export default function SelectSchedule() { }; const onDateSelect = (date) => { - const schedule = schedules.find(schedule => schedule.startTime.startsWith(date.format('YYYY-MM-DD'))); + const schedule = schedules.find(schedule => schedule.startDate.startsWith(date.format('YYYY-MM-DD'))); if (schedule) { setSelectedDateId(schedule.id); } diff --git a/ticketping/src/pages/seat/Seat.js b/ticketping/src/pages/seat/Seat.js index 1252123..2e58d72 100644 --- a/ticketping/src/pages/seat/Seat.js +++ b/ticketping/src/pages/seat/Seat.js @@ -1,30 +1,111 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; +import { useParams } from 'react-router-dom'; +import { axiosInstance } from "../../api"; +import { useAppContext } from "../../store"; import SeatLayout from './SeatLayout'; import '../../style/Seat.css'; function Seat() { - const rows = 5; - const columns = 10; - const performanceName = "햄릿"; - - const grades = ['S', 'A', 'B', 'B', 'B']; - - const seats = Array.from({ length: rows * columns }, (_, index) => ({ - grade: grades[Math.floor(index / columns)], - price: 10000, - reserved: index % 7 === 0, - })); - + const { performanceId, scheduleId } = useParams(); + const { store: { jwtToken } } = useAppContext(); + const [seats, setSeats] = useState([]); const [selectedSeat, setSelectedSeat] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + const [isBooking, setIsBooking] = useState(false); + + // rows, columns, performanceName은 performance 정보 받아온 거에 맞춰서 바꾸기 + const rows = 10; + const columns = 5; + const performanceName = "햄릿"; + const grades = ['S', 'S', 'A', 'A', 'B', 'B', 'B', 'B', 'B', 'B', 'B']; + const headers = { Authorization: jwtToken }; + + useEffect(() => { + const fetchSeats = async () => { + try { + const response = await axiosInstance.get( + `/api/v1/schedules/${scheduleId}/seats`, + { headers } + ); + + const seatData = response.data.data.map((seat) => ({ + row: seat.row, + col: seat.col, + grade: seat.seatGrade, + price: seat.cost, + reserved: seat.seatStatus !== "AVALIABLE", + seatId: seat.seatId, + })); + + const sortedSeats = seatData.sort((a, b) => { + if (a.row === b.row) { + return a.col - b.col; + } + return a.row - b.row; + }); + + setSeats(sortedSeats); + } catch (error) { + console.error('Error fetching seat data:', error); + setError("좌석 정보를 불러오는데 실패하였습니다!"); + } finally { + setIsLoading(false); + } + }; + + fetchSeats(); + }, []); const handleSeatSelect = (seatId) => { - if (selectedSeat === seatId) { - setSelectedSeat(null); - } else { - setSelectedSeat(seatId); + setSelectedSeat(selectedSeat === seatId ? null : seatId); + }; + + const handleBooking = async () => { + if (selectedSeat === null) return; + + const selectedSeatData = seats[selectedSeat]; + if (!selectedSeatData) { + alert("선택된 좌석이 유효하지 않습니다."); + return; + } + + const bookingData = { + seatId: selectedSeatData.seatId, + scheduleId: scheduleId, + }; + + try { + setIsBooking(true); + await axiosInstance.post( + `http://localhost:10001/api/v1/orders?performanceId=${performanceId}`, + bookingData, + { headers } + ); + alert("예매 성공!"); + } catch (error) { + console.error("Error during booking:", error); + if (error.response) { + alert(error.response.data.message || "서버 오류가 발생했습니다."); + } else if (error.request) { + alert("서버 응답이 없습니다. 잠시 후 다시 시도해주세요."); + } else { + alert("알 수 없는 오류가 발생했습니다."); + } + } finally { + setIsBooking(false); } }; + if (isLoading) { + return
Loading seats...
; + } + + if (error) { + console.error("Error state:", error); + return

{error}

; + } + return (
@@ -42,7 +123,9 @@ function Seat() {

{performanceName}

{selectedSeat !== null ? (
-

좌석: {Math.floor(selectedSeat / columns) + 1}행 {selectedSeat % columns + 1}열

+

+ 좌석: {seats[selectedSeat].row}열 {seats[selectedSeat].col}행 +

등급: {seats[selectedSeat].grade}

가격: {seats[selectedSeat].price}원

@@ -52,9 +135,10 @@ function Seat() {
diff --git a/ticketping/src/pages/seat/SeatLayout.js b/ticketping/src/pages/seat/SeatLayout.js index bbe871b..57ed74e 100644 --- a/ticketping/src/pages/seat/SeatLayout.js +++ b/ticketping/src/pages/seat/SeatLayout.js @@ -2,7 +2,6 @@ import React from 'react'; import '../../style/SeatLayout.css'; function SeatLayout({ rows, columns, grades, seats, selectedSeat, onSeatSelect }) { - const handleSeatClick = (seatId) => { if (!seats[seatId].reserved) { onSeatSelect(seatId); @@ -11,41 +10,39 @@ function SeatLayout({ rows, columns, grades, seats, selectedSeat, onSeatSelect } const seatLayout = []; - // 공연장 추가 seatLayout.push(
공연장
); - for (let i = 0; i < rows; i++) { + for (let i = 1; i <= rows; i++) { const row = []; - const grade = seats[i * columns].grade; // 수정: 올바른 grade를 참조 row.push(
- {grades[i]} + {grades[i - 1]}
); - for (let j = 0; j < columns; j++) { - const seatId = i * columns + j; - const seat = seats[seatId]; - const isReserved = seat.reserved; - const isSelected = seatId === selectedSeat; + for (let j = 1; j <= columns; j++) { + const seat = seats.find((s) => s.row === i && s.col === j); + const isReserved = seat?.reserved || false; + const isSelected = seat && selectedSeat === seats.indexOf(seat); row.push(
handleSeatClick(seatId)} - title={`등급: ${seat.grade}, 가격: ${seat.price}`} + onClick={() => handleSeatClick(seats.indexOf(seat))} + title={seat ? `등급: ${seat.grade}, 가격: ${seat.price}원` : ''} > - {i + 1}-{j + 1} + {i}-{j}
); } + seatLayout.push(
{row} diff --git a/ticketping/src/style/Seat.css b/ticketping/src/style/Seat.css index bb95875..3e2c36b 100644 --- a/ticketping/src/style/Seat.css +++ b/ticketping/src/style/Seat.css @@ -10,7 +10,7 @@ justify-content: center; align-items: center; width: 100%; - height: 550px; + min-height: 550px; } .page-detail { From 61038f924fa5efef180bfbe4e44229c8554503a3 Mon Sep 17 00:00:00 2001 From: mii2026 Date: Wed, 25 Dec 2024 10:31:57 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[fix]=20=EC=A2=8C=EC=84=9D=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=ED=99=95=EC=9D=B8=20=EC=98=A4=ED=83=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ticketping/src/pages/seat/Seat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ticketping/src/pages/seat/Seat.js b/ticketping/src/pages/seat/Seat.js index 2e58d72..e47badf 100644 --- a/ticketping/src/pages/seat/Seat.js +++ b/ticketping/src/pages/seat/Seat.js @@ -34,7 +34,7 @@ function Seat() { col: seat.col, grade: seat.seatGrade, price: seat.cost, - reserved: seat.seatStatus !== "AVALIABLE", + reserved: seat.seatStatus !== "AVAILABLE", seatId: seat.seatId, }));