Skip to content

Commit f60c1be

Browse files
committed
♻️ refactor:좌석 렌더링 분리
Issue Resolved: #
1 parent f666d25 commit f60c1be

File tree

6 files changed

+240
-169
lines changed

6 files changed

+240
-169
lines changed

front/src/constants/reservation.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const SEAT_COUNT_LIST = [1, 2, 3, 4] as const;

front/src/pages/ReservationPage/SeatCountContent.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { postSeatCount } from '@/api/booking.ts';
66
import Button from '@/components/common/Button';
77
import Separator from '@/components/common/Separator.tsx';
88

9+
import { SEAT_COUNT_LIST } from '@/constants/reservation.ts';
10+
import type { SeatCount } from '@/type/reservation.ts';
911
import { useMutation } from '@tanstack/react-query';
1012
import { cx } from 'class-variance-authority';
1113

@@ -15,7 +17,6 @@ interface ISeatCountContentProps {
1517
goNextStep: () => void;
1618
}
1719
//section 선택 페이지는 좌석 선택시에도 사용된다\
18-
export type SeatCount = (typeof SEAT_COUNT_LIST)[number];
1920

2021
export default function SeatCountContent({ selectCount, goNextStep, seatCount }: ISeatCountContentProps) {
2122
const { mutate: postSeatCountMutate, isPending } = useMutation({ mutationFn: postSeatCount });
@@ -39,7 +40,11 @@ export default function SeatCountContent({ selectCount, goNextStep, seatCount }:
3940
<Separator direction="row" />
4041
<label htmlFor="seatCount" className="flex flex-col gap-4">
4142
<span className="text-heading2">좌석 개수</span>
42-
<select id="seatCount" className="w-full rounded border px-4 py-2" onChange={selectSeatCount}>
43+
<select
44+
id="seatCount"
45+
className="w-full rounded border px-4 py-2"
46+
defaultValue={seatCount}
47+
onChange={selectSeatCount}>
4348
{SEAT_COUNT_LIST.map((count) => (
4449
<option key={count} className="" value={count}>{`${count} 개`}</option>
4550
))}
@@ -66,6 +71,4 @@ export default function SeatCountContent({ selectCount, goNextStep, seatCount }:
6671
</div>
6772
);
6873
}
69-
70-
const SEAT_COUNT_LIST = [1, 2, 3, 4] as const;
7174
const HELP_MESSAGE_LIST = ['최대 4매까지 선택 가능합니다.'];
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
import { useEffect, useRef, useState } from 'react';
2+
import { useParams } from 'react-router-dom';
3+
4+
import { BASE_URL } from '@/api/axios.ts';
5+
import type { PostSeatData } from '@/api/booking.ts';
6+
import { postSeat } from '@/api/booking.ts';
7+
8+
import { toast } from '@/components/Toast/index.ts';
9+
import Icon from '@/components/common/Icon.tsx';
10+
11+
import type { SelectedSeat } from '@/pages/ReservationPage/SectionAndSeat.tsx';
12+
13+
import { API } from '@/constants/index.ts';
14+
import type { Section } from '@/type/index.ts';
15+
import { useMutation, useMutationState } from '@tanstack/react-query';
16+
17+
interface SeatMapProps {
18+
selectedSection: Section;
19+
selectedSectionIndex: number;
20+
setSelectedSeats: (seats: SelectedSeat[]) => void;
21+
maxSelectCount: number;
22+
selectedSeats: SelectedSeat[];
23+
}
24+
25+
export default function SeatMap({
26+
selectedSection,
27+
selectedSectionIndex,
28+
setSelectedSeats,
29+
maxSelectCount,
30+
selectedSeats,
31+
}: SeatMapProps) {
32+
const { eventId } = useParams();
33+
//TODO 이름 헷갈림
34+
const [seatStatusList, setSeatStatusList] = useState<boolean[][]>([]);
35+
const seatStatus = seatStatusList[selectedSectionIndex];
36+
const eventSourceRef = useRef<null | EventSource>(null);
37+
const { mutate: pickSeat } = useMutation({
38+
mutationFn: postSeat,
39+
mutationKey: PICK_SEAT_MUTATION_KEY_LIST,
40+
onError: (_, data) => {
41+
const { seatIndex, sectionIndex } = data;
42+
const filtered = selectedSeats.filter(
43+
(seat) => seat.seatIndex !== seatIndex || seat.sectionIndex !== sectionIndex,
44+
);
45+
setSelectedSeats([...filtered]);
46+
toast.error('좌석 선택/취소에 실패했습니다');
47+
},
48+
throwOnError: false,
49+
});
50+
51+
const reservingList = useMutationState<PostSeatData>({
52+
filters: {
53+
mutationKey: PICK_SEAT_MUTATION_KEY_LIST,
54+
status: 'pending',
55+
predicate: (mutation) => {
56+
return mutation.state.variables.expectedStatus === 'reserved';
57+
},
58+
},
59+
select: (mutation) => mutation.state.variables as PostSeatData,
60+
});
61+
useEffect(() => {
62+
if (eventSourceRef.current === null) {
63+
eventSourceRef.current = new EventSource(`${BASE_URL}${API.BOOKING.GET_SEATS_SSE(Number(eventId))}`, {
64+
withCredentials: true,
65+
});
66+
eventSourceRef.current.onmessage = (event) => {
67+
const { seatStatus } = JSON.parse(event.data);
68+
if (seatStatus) {
69+
setSeatStatusList(seatStatus);
70+
}
71+
};
72+
}
73+
return () => {
74+
if (eventSourceRef.current) {
75+
eventSourceRef.current.close();
76+
eventSourceRef.current = null;
77+
}
78+
};
79+
}, [eventId]);
80+
const canRender = seatStatusList.length !== 0;
81+
return (
82+
<>
83+
{/* <Loading /> */}
84+
{canRender ? (
85+
renderSeatMap(
86+
selectedSection,
87+
selectedSectionIndex,
88+
seatStatus,
89+
setSelectedSeats,
90+
maxSelectCount,
91+
selectedSeats,
92+
pickSeat,
93+
Number(eventId!),
94+
reservingList,
95+
)
96+
) : (
97+
<Loading />
98+
)}
99+
</>
100+
);
101+
}
102+
const Loading = () => {
103+
return (
104+
<div className="absolute flex w-full items-center justify-center">
105+
<div className="flex flex-col items-center gap-4">
106+
<Icon iconName="Loading" className="h-16 w-16 animate-spin" color={'primary'} />
107+
<span className="text-heading3 text-typo">loading..</span>
108+
</div>
109+
</div>
110+
);
111+
};
112+
113+
const renderSeatMap = (
114+
selectedSection: Section,
115+
selectedSectionIndex: number,
116+
seatStatus: boolean[],
117+
setSelectedSeats: (seats: SelectedSeat[]) => void,
118+
maxSelectCount: number,
119+
selectedSeats: SelectedSeat[],
120+
pickSeat: (
121+
data: PostSeatData,
122+
mutateOption?: {
123+
onSuccess?: () => void;
124+
onError?: () => void;
125+
},
126+
) => void,
127+
eventId: number,
128+
reservingList: PostSeatData[],
129+
) => {
130+
let columnCount = 1;
131+
const { name, seats, colLen } = selectedSection;
132+
133+
return seats.map((seat, index) => {
134+
const rowsCount = Math.floor(index / colLen) + 1;
135+
const isNewLine = index % colLen === 0;
136+
if (isNewLine) columnCount = 1;
137+
const seatName = seat ? `${name}구역 ${rowsCount}${columnCount}열` : null;
138+
const isMine = seatName && selectedSeats.some((selected) => selected.name == seatName);
139+
140+
const isReserving = reservingList.some(
141+
(reserve) => reserve.seatIndex === index && reserve.sectionIndex === selectedSectionIndex,
142+
);
143+
const isOthers = !seatStatus[index];
144+
//TODO 삼항 연산자 제거
145+
const stateClass = !seat
146+
? 'bg-transparent pointer-events-none'
147+
: isReserving
148+
? 'bg-warning pointer-events-none'
149+
: isMine
150+
? 'bg-success cursor-pointer'
151+
: isOthers
152+
? `bg-surface-sub pointer-events-none`
153+
: 'bg-primary cursor-pointer';
154+
if (seat) columnCount++;
155+
return (
156+
<div
157+
className={`h-6 w-6 ${stateClass}`}
158+
data-name={seatName}
159+
onClick={() => {
160+
const selectedCount = selectedSeats.length;
161+
if (isMine) {
162+
const filtered = selectedSeats.filter((seat) => seatName !== seat.name);
163+
pickSeat(
164+
{
165+
sectionIndex: selectedSectionIndex,
166+
seatIndex: index,
167+
expectedStatus: 'deleted',
168+
eventId,
169+
},
170+
{
171+
onSuccess: () => {
172+
toast.warning(`${seatName!} 좌석을 취소했습니다`);
173+
},
174+
},
175+
);
176+
setSelectedSeats(filtered);
177+
return;
178+
}
179+
180+
if (maxSelectCount <= selectedCount) return;
181+
pickSeat(
182+
{
183+
sectionIndex: selectedSectionIndex,
184+
seatIndex: index,
185+
expectedStatus: 'reserved',
186+
eventId,
187+
},
188+
{
189+
onSuccess: () => {
190+
toast.success(`${seatName!} 좌석 선택에\n성공했습니다`);
191+
},
192+
},
193+
);
194+
setSelectedSeats([
195+
...selectedSeats,
196+
{ seatIndex: index, sectionIndex: selectedSectionIndex, name: seatName! },
197+
]);
198+
}}
199+
/>
200+
);
201+
});
202+
};
203+
204+
const PICK_SEAT_MUTATION_KEY_LIST = ['seat'];

0 commit comments

Comments
 (0)