@nex125/seatmap-viewer 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +535 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -3
- package/dist/index.d.ts +27 -3
- package/dist/index.js +536 -8
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -1,17 +1,41 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
2
3
|
import { Venue, SeatStatus } from '@nex125/seatmap-core';
|
|
3
4
|
import { TooltipData } from '@nex125/seatmap-react';
|
|
4
5
|
export { useSeatStatus } from '@nex125/seatmap-react';
|
|
5
6
|
|
|
7
|
+
interface SeatmapCartSeat {
|
|
8
|
+
seatId: string;
|
|
9
|
+
seatLabel: string;
|
|
10
|
+
rowLabel: string | null;
|
|
11
|
+
sectionId: string | null;
|
|
12
|
+
sectionLabel: string | null;
|
|
13
|
+
categoryId: string;
|
|
14
|
+
categoryName: string;
|
|
15
|
+
unitPrice: number;
|
|
16
|
+
}
|
|
17
|
+
interface SeatmapCartPayload {
|
|
18
|
+
venueId: string;
|
|
19
|
+
venueName: string;
|
|
20
|
+
seats: SeatmapCartSeat[];
|
|
21
|
+
totalSelectedSeats: number;
|
|
22
|
+
totalCost: number;
|
|
23
|
+
}
|
|
24
|
+
interface SeatmapCartEvent {
|
|
25
|
+
type: "cart-proceed-clicked";
|
|
26
|
+
payload: SeatmapCartPayload;
|
|
27
|
+
}
|
|
6
28
|
interface SeatmapViewerProps {
|
|
7
29
|
venue: Venue;
|
|
8
30
|
onSeatClick?: (seatId: string, sectionId: string) => void;
|
|
9
31
|
onSeatHover?: (seatId: string | null, sectionId: string | null) => void;
|
|
10
32
|
onSelectionChange?: (seatIds: string[]) => void;
|
|
11
33
|
onStatusUpdate?: (seatId: string, status: SeatStatus) => void;
|
|
12
|
-
renderTooltip?: (data: TooltipData) =>
|
|
34
|
+
renderTooltip?: (data: TooltipData) => ReactNode;
|
|
35
|
+
showLabels?: boolean;
|
|
13
36
|
className?: string;
|
|
37
|
+
onCartEvent?: (event: SeatmapCartEvent) => void;
|
|
14
38
|
}
|
|
15
|
-
declare function SeatmapViewer({ venue, onSeatClick, onSeatHover, renderTooltip, className, }: SeatmapViewerProps): react_jsx_runtime.JSX.Element;
|
|
39
|
+
declare function SeatmapViewer({ venue, onSeatClick, onSeatHover, onSelectionChange, renderTooltip, showLabels, className, onCartEvent, }: SeatmapViewerProps): react_jsx_runtime.JSX.Element;
|
|
16
40
|
|
|
17
|
-
export { SeatmapViewer, type SeatmapViewerProps };
|
|
41
|
+
export { type SeatmapCartEvent, type SeatmapCartPayload, type SeatmapCartSeat, SeatmapViewer, type SeatmapViewerProps };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,41 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
2
3
|
import { Venue, SeatStatus } from '@nex125/seatmap-core';
|
|
3
4
|
import { TooltipData } from '@nex125/seatmap-react';
|
|
4
5
|
export { useSeatStatus } from '@nex125/seatmap-react';
|
|
5
6
|
|
|
7
|
+
interface SeatmapCartSeat {
|
|
8
|
+
seatId: string;
|
|
9
|
+
seatLabel: string;
|
|
10
|
+
rowLabel: string | null;
|
|
11
|
+
sectionId: string | null;
|
|
12
|
+
sectionLabel: string | null;
|
|
13
|
+
categoryId: string;
|
|
14
|
+
categoryName: string;
|
|
15
|
+
unitPrice: number;
|
|
16
|
+
}
|
|
17
|
+
interface SeatmapCartPayload {
|
|
18
|
+
venueId: string;
|
|
19
|
+
venueName: string;
|
|
20
|
+
seats: SeatmapCartSeat[];
|
|
21
|
+
totalSelectedSeats: number;
|
|
22
|
+
totalCost: number;
|
|
23
|
+
}
|
|
24
|
+
interface SeatmapCartEvent {
|
|
25
|
+
type: "cart-proceed-clicked";
|
|
26
|
+
payload: SeatmapCartPayload;
|
|
27
|
+
}
|
|
6
28
|
interface SeatmapViewerProps {
|
|
7
29
|
venue: Venue;
|
|
8
30
|
onSeatClick?: (seatId: string, sectionId: string) => void;
|
|
9
31
|
onSeatHover?: (seatId: string | null, sectionId: string | null) => void;
|
|
10
32
|
onSelectionChange?: (seatIds: string[]) => void;
|
|
11
33
|
onStatusUpdate?: (seatId: string, status: SeatStatus) => void;
|
|
12
|
-
renderTooltip?: (data: TooltipData) =>
|
|
34
|
+
renderTooltip?: (data: TooltipData) => ReactNode;
|
|
35
|
+
showLabels?: boolean;
|
|
13
36
|
className?: string;
|
|
37
|
+
onCartEvent?: (event: SeatmapCartEvent) => void;
|
|
14
38
|
}
|
|
15
|
-
declare function SeatmapViewer({ venue, onSeatClick, onSeatHover, renderTooltip, className, }: SeatmapViewerProps): react_jsx_runtime.JSX.Element;
|
|
39
|
+
declare function SeatmapViewer({ venue, onSeatClick, onSeatHover, onSelectionChange, renderTooltip, showLabels, className, onCartEvent, }: SeatmapViewerProps): react_jsx_runtime.JSX.Element;
|
|
16
40
|
|
|
17
|
-
export { SeatmapViewer, type SeatmapViewerProps };
|
|
41
|
+
export { type SeatmapCartEvent, type SeatmapCartPayload, type SeatmapCartSeat, SeatmapViewer, type SeatmapViewerProps };
|
package/dist/index.js
CHANGED
|
@@ -1,25 +1,553 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useState, useRef, useMemo, useEffect, useCallback } from 'react';
|
|
2
|
+
import { AVAILABLE_STATUS_ID } from '@nex125/seatmap-core';
|
|
3
|
+
import { SeatmapProvider, useSelection, SeatmapCanvas, TooltipOverlay } from '@nex125/seatmap-react';
|
|
2
4
|
export { useSeatStatus } from '@nex125/seatmap-react';
|
|
3
5
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
6
|
|
|
5
7
|
// src/SeatmapViewer.tsx
|
|
6
|
-
|
|
8
|
+
var legendContainerStyle = {
|
|
9
|
+
position: "absolute",
|
|
10
|
+
top: 12,
|
|
11
|
+
left: 12,
|
|
12
|
+
zIndex: 10,
|
|
13
|
+
pointerEvents: "none",
|
|
14
|
+
background: "rgba(18, 18, 34, 0.88)",
|
|
15
|
+
border: "1px solid rgba(110, 110, 150, 0.45)",
|
|
16
|
+
borderRadius: 8,
|
|
17
|
+
padding: "10px 12px",
|
|
18
|
+
minWidth: 150,
|
|
19
|
+
color: "#e0e0e0",
|
|
20
|
+
fontFamily: "system-ui",
|
|
21
|
+
fontSize: 12,
|
|
22
|
+
backdropFilter: "blur(2px)"
|
|
23
|
+
};
|
|
24
|
+
var legendHeadingStyle = {
|
|
25
|
+
margin: 0,
|
|
26
|
+
fontSize: 11,
|
|
27
|
+
fontWeight: 600,
|
|
28
|
+
letterSpacing: 0.2,
|
|
29
|
+
color: "#b9b9d6"
|
|
30
|
+
};
|
|
31
|
+
var legendListStyle = {
|
|
32
|
+
listStyle: "none",
|
|
33
|
+
margin: "6px 0 0",
|
|
34
|
+
padding: 0,
|
|
35
|
+
display: "flex",
|
|
36
|
+
flexDirection: "column",
|
|
37
|
+
gap: 4
|
|
38
|
+
};
|
|
39
|
+
var legendItemStyle = {
|
|
40
|
+
display: "flex",
|
|
41
|
+
alignItems: "center",
|
|
42
|
+
gap: 6,
|
|
43
|
+
minWidth: 0
|
|
44
|
+
};
|
|
45
|
+
var legendSwatchStyle = {
|
|
46
|
+
width: 10,
|
|
47
|
+
height: 10,
|
|
48
|
+
borderRadius: 2,
|
|
49
|
+
flexShrink: 0,
|
|
50
|
+
border: "1px solid rgba(255, 255, 255, 0.25)"
|
|
51
|
+
};
|
|
52
|
+
var cartChipStyle = {
|
|
53
|
+
position: "absolute",
|
|
54
|
+
right: 12,
|
|
55
|
+
bottom: 12,
|
|
56
|
+
zIndex: 20,
|
|
57
|
+
border: "1px solid rgba(110, 110, 150, 0.65)",
|
|
58
|
+
background: "rgba(24, 24, 42, 0.95)",
|
|
59
|
+
color: "#ececff",
|
|
60
|
+
borderRadius: 999,
|
|
61
|
+
padding: "8px 14px",
|
|
62
|
+
fontSize: 12,
|
|
63
|
+
fontWeight: 600,
|
|
64
|
+
fontFamily: "system-ui",
|
|
65
|
+
cursor: "pointer",
|
|
66
|
+
boxShadow: "0 8px 18px rgba(0, 0, 0, 0.35)"
|
|
67
|
+
};
|
|
68
|
+
var cartPopupStyle = {
|
|
69
|
+
position: "absolute",
|
|
70
|
+
right: 0,
|
|
71
|
+
bottom: 0,
|
|
72
|
+
zIndex: 30,
|
|
73
|
+
maxHeight: "72vh",
|
|
74
|
+
display: "flex",
|
|
75
|
+
flexDirection: "column",
|
|
76
|
+
background: "rgba(17, 17, 34, 0.98)",
|
|
77
|
+
borderTop: "1px solid rgba(110, 110, 150, 0.65)",
|
|
78
|
+
borderLeft: "1px solid rgba(110, 110, 150, 0.65)",
|
|
79
|
+
borderTopLeftRadius: 12,
|
|
80
|
+
overflow: "hidden",
|
|
81
|
+
boxShadow: "-8px -8px 20px rgba(0, 0, 0, 0.35)"
|
|
82
|
+
};
|
|
83
|
+
var cartIconButtonStyle = {
|
|
84
|
+
width: 24,
|
|
85
|
+
height: 24,
|
|
86
|
+
display: "inline-flex",
|
|
87
|
+
alignItems: "center",
|
|
88
|
+
justifyContent: "center",
|
|
89
|
+
borderRadius: 5,
|
|
90
|
+
border: "1px solid #4a4a6a",
|
|
91
|
+
background: "#262648",
|
|
92
|
+
color: "#ececff",
|
|
93
|
+
fontSize: 14,
|
|
94
|
+
cursor: "pointer"
|
|
95
|
+
};
|
|
96
|
+
function getEffectiveCategoryPrice(category) {
|
|
97
|
+
if (!category) return 0;
|
|
98
|
+
if (category.isPriceOverridden && Number.isFinite(category.overriddenPrice)) {
|
|
99
|
+
return category.overriddenPrice ?? 0;
|
|
100
|
+
}
|
|
101
|
+
return Number.isFinite(category.backendPrice) ? category.backendPrice : 0;
|
|
102
|
+
}
|
|
103
|
+
function formatMoney(value) {
|
|
104
|
+
return `$${value.toFixed(2)}`;
|
|
105
|
+
}
|
|
106
|
+
function TrashIcon() {
|
|
107
|
+
return /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
|
|
108
|
+
"path",
|
|
109
|
+
{
|
|
110
|
+
d: "M9 3h6l1 2h4v2H4V5h4l1-2Zm1 7h2v8h-2v-8Zm4 0h2v8h-2v-8ZM7 10h2v8H7v-8Zm-1 12h12a2 2 0 0 0 2-2V8H4v12a2 2 0 0 0 2 2Z",
|
|
111
|
+
fill: "currentColor"
|
|
112
|
+
}
|
|
113
|
+
) });
|
|
114
|
+
}
|
|
115
|
+
function ViewerContent({
|
|
7
116
|
venue,
|
|
8
117
|
onSeatClick,
|
|
9
118
|
onSeatHover,
|
|
119
|
+
onSelectionChange,
|
|
10
120
|
renderTooltip,
|
|
11
|
-
|
|
121
|
+
showLabels,
|
|
122
|
+
onCartEvent
|
|
12
123
|
}) {
|
|
13
|
-
|
|
124
|
+
const { selectedSeatIds, setSelection } = useSelection();
|
|
125
|
+
const [isCartOpen, setIsCartOpen] = useState(false);
|
|
126
|
+
const [lastUserAnchorByCategory, setLastUserAnchorByCategory] = useState({});
|
|
127
|
+
const [viewportWidth, setViewportWidth] = useState(
|
|
128
|
+
() => typeof window === "undefined" ? 1280 : window.innerWidth
|
|
129
|
+
);
|
|
130
|
+
const previousSelectedRef = useRef([]);
|
|
131
|
+
const pendingSeatClickRef = useRef(null);
|
|
132
|
+
const showStatuses = venue.seatStatuses.length > 0;
|
|
133
|
+
const showCategories = venue.categories.length > 0;
|
|
134
|
+
const selectedSeatIdArray = useMemo(() => Array.from(selectedSeatIds), [selectedSeatIds]);
|
|
135
|
+
const seatDetailsById = useMemo(() => {
|
|
136
|
+
const categoryMap = new Map(venue.categories.map((category) => [category.id, category]));
|
|
137
|
+
const next = /* @__PURE__ */ new Map();
|
|
138
|
+
for (const section of venue.sections) {
|
|
139
|
+
const cos = Math.cos(section.rotation);
|
|
140
|
+
const sin = Math.sin(section.rotation);
|
|
141
|
+
for (const row of section.rows) {
|
|
142
|
+
for (const seat of row.seats) {
|
|
143
|
+
const category = categoryMap.get(seat.categoryId);
|
|
144
|
+
next.set(seat.id, {
|
|
145
|
+
seatId: seat.id,
|
|
146
|
+
seatLabel: seat.label,
|
|
147
|
+
rowLabel: row.label,
|
|
148
|
+
sectionId: section.id,
|
|
149
|
+
sectionLabel: section.label,
|
|
150
|
+
categoryId: seat.categoryId,
|
|
151
|
+
categoryName: category?.name ?? "Unknown",
|
|
152
|
+
unitPrice: getEffectiveCategoryPrice(category),
|
|
153
|
+
status: seat.status,
|
|
154
|
+
x: section.position.x + seat.position.x * cos - seat.position.y * sin,
|
|
155
|
+
y: section.position.y + seat.position.x * sin + seat.position.y * cos
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
for (const table of venue.tables) {
|
|
161
|
+
for (const seat of table.seats) {
|
|
162
|
+
const category = categoryMap.get(seat.categoryId);
|
|
163
|
+
next.set(seat.id, {
|
|
164
|
+
seatId: seat.id,
|
|
165
|
+
seatLabel: seat.label,
|
|
166
|
+
rowLabel: null,
|
|
167
|
+
sectionId: table.id,
|
|
168
|
+
sectionLabel: `Table ${table.label}`,
|
|
169
|
+
categoryId: seat.categoryId,
|
|
170
|
+
categoryName: category?.name ?? "Unknown",
|
|
171
|
+
unitPrice: getEffectiveCategoryPrice(category),
|
|
172
|
+
status: seat.status,
|
|
173
|
+
x: table.position.x + seat.position.x,
|
|
174
|
+
y: table.position.y + seat.position.y
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return next;
|
|
179
|
+
}, [venue]);
|
|
180
|
+
const selectedSeats = useMemo(() => {
|
|
181
|
+
const ordered = [];
|
|
182
|
+
for (const selectedSeatId of selectedSeatIdArray) {
|
|
183
|
+
const seat = seatDetailsById.get(selectedSeatId);
|
|
184
|
+
if (seat) ordered.push(seat);
|
|
185
|
+
}
|
|
186
|
+
return ordered;
|
|
187
|
+
}, [seatDetailsById, selectedSeatIdArray]);
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
onSelectionChange?.(selectedSeatIdArray);
|
|
190
|
+
}, [onSelectionChange, selectedSeatIdArray]);
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
const previous = new Set(previousSelectedRef.current);
|
|
193
|
+
const added = selectedSeatIdArray.filter((seatId) => !previous.has(seatId));
|
|
194
|
+
const clickedSeatId = pendingSeatClickRef.current;
|
|
195
|
+
if (clickedSeatId && added.includes(clickedSeatId)) {
|
|
196
|
+
const clickedSeat = seatDetailsById.get(clickedSeatId);
|
|
197
|
+
if (clickedSeat) {
|
|
198
|
+
setLastUserAnchorByCategory((current) => ({
|
|
199
|
+
...current,
|
|
200
|
+
[clickedSeat.categoryId]: clickedSeatId
|
|
201
|
+
}));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
previousSelectedRef.current = selectedSeatIdArray;
|
|
205
|
+
pendingSeatClickRef.current = null;
|
|
206
|
+
}, [seatDetailsById, selectedSeatIdArray]);
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
if (typeof window === "undefined") return;
|
|
209
|
+
const onResize = () => setViewportWidth(window.innerWidth);
|
|
210
|
+
window.addEventListener("resize", onResize);
|
|
211
|
+
return () => window.removeEventListener("resize", onResize);
|
|
212
|
+
}, []);
|
|
213
|
+
const cartSeats = useMemo(
|
|
214
|
+
() => selectedSeats.map((seat) => ({
|
|
215
|
+
seatId: seat.seatId,
|
|
216
|
+
seatLabel: seat.seatLabel,
|
|
217
|
+
rowLabel: seat.rowLabel,
|
|
218
|
+
sectionId: seat.sectionId,
|
|
219
|
+
sectionLabel: seat.sectionLabel,
|
|
220
|
+
categoryId: seat.categoryId,
|
|
221
|
+
categoryName: seat.categoryName,
|
|
222
|
+
unitPrice: seat.unitPrice
|
|
223
|
+
})),
|
|
224
|
+
[selectedSeats]
|
|
225
|
+
);
|
|
226
|
+
const totalSelectedSeats = useMemo(() => cartSeats.length, [cartSeats]);
|
|
227
|
+
const totalCost = useMemo(() => cartSeats.reduce((sum, seat) => sum + seat.unitPrice, 0), [cartSeats]);
|
|
228
|
+
const groupedSeatsList = useMemo(() => {
|
|
229
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
230
|
+
for (const seat of cartSeats) {
|
|
231
|
+
const existing = grouped.get(seat.categoryId);
|
|
232
|
+
if (existing) {
|
|
233
|
+
existing.seats.push(seat);
|
|
234
|
+
} else {
|
|
235
|
+
grouped.set(seat.categoryId, {
|
|
236
|
+
categoryName: seat.categoryName,
|
|
237
|
+
unitPrice: seat.unitPrice,
|
|
238
|
+
seats: [seat]
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const ordered = [];
|
|
243
|
+
const selectedSet = new Set(selectedSeatIdArray);
|
|
244
|
+
const availableToAddByCategory = /* @__PURE__ */ new Map();
|
|
245
|
+
for (const seat of seatDetailsById.values()) {
|
|
246
|
+
if (seat.status !== AVAILABLE_STATUS_ID || selectedSet.has(seat.seatId)) continue;
|
|
247
|
+
availableToAddByCategory.set(
|
|
248
|
+
seat.categoryId,
|
|
249
|
+
(availableToAddByCategory.get(seat.categoryId) ?? 0) + 1
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
for (const category of venue.categories) {
|
|
253
|
+
const group = grouped.get(category.id);
|
|
254
|
+
if (!group) continue;
|
|
255
|
+
ordered.push({
|
|
256
|
+
categoryId: category.id,
|
|
257
|
+
categoryName: group.categoryName,
|
|
258
|
+
unitPrice: group.unitPrice,
|
|
259
|
+
seats: group.seats,
|
|
260
|
+
availableToAdd: availableToAddByCategory.get(category.id) ?? 0
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
for (const [categoryId, group] of grouped.entries()) {
|
|
264
|
+
if (venue.categories.some((category) => category.id === categoryId)) continue;
|
|
265
|
+
ordered.push({
|
|
266
|
+
categoryId,
|
|
267
|
+
categoryName: group.categoryName,
|
|
268
|
+
unitPrice: group.unitPrice,
|
|
269
|
+
seats: group.seats,
|
|
270
|
+
availableToAdd: availableToAddByCategory.get(categoryId) ?? 0
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
return ordered;
|
|
274
|
+
}, [cartSeats, seatDetailsById, selectedSeatIdArray, venue.categories]);
|
|
275
|
+
const cartPayload = useMemo(
|
|
276
|
+
() => ({
|
|
277
|
+
venueId: venue.id,
|
|
278
|
+
venueName: venue.name,
|
|
279
|
+
seats: cartSeats,
|
|
280
|
+
totalSelectedSeats,
|
|
281
|
+
totalCost
|
|
282
|
+
}),
|
|
283
|
+
[venue.id, venue.name, cartSeats, totalSelectedSeats, totalCost]
|
|
284
|
+
);
|
|
285
|
+
const emitCartEvent = useCallback(
|
|
286
|
+
(event) => {
|
|
287
|
+
onCartEvent?.(event);
|
|
288
|
+
},
|
|
289
|
+
[onCartEvent]
|
|
290
|
+
);
|
|
291
|
+
const removeSeatFromSelection = useCallback(
|
|
292
|
+
(seatId) => {
|
|
293
|
+
const next = selectedSeatIdArray.filter((id) => id !== seatId);
|
|
294
|
+
setSelection(next);
|
|
295
|
+
},
|
|
296
|
+
[selectedSeatIdArray, setSelection]
|
|
297
|
+
);
|
|
298
|
+
const handleSeatCanvasClick = useCallback(
|
|
299
|
+
(seatId, sectionId) => {
|
|
300
|
+
pendingSeatClickRef.current = seatId;
|
|
301
|
+
onSeatClick?.(seatId, sectionId);
|
|
302
|
+
},
|
|
303
|
+
[onSeatClick]
|
|
304
|
+
);
|
|
305
|
+
const handleAddSeatInCategory = useCallback(
|
|
306
|
+
(categoryId) => {
|
|
307
|
+
const selectedSet = new Set(selectedSeatIdArray);
|
|
308
|
+
const selectedInCategory = selectedSeatIdArray.filter(
|
|
309
|
+
(selectedSeatId) => seatDetailsById.get(selectedSeatId)?.categoryId === categoryId
|
|
310
|
+
);
|
|
311
|
+
const anchorSeatId = (lastUserAnchorByCategory[categoryId] && seatDetailsById.has(lastUserAnchorByCategory[categoryId]) ? lastUserAnchorByCategory[categoryId] : null) ?? selectedInCategory[selectedInCategory.length - 1] ?? null;
|
|
312
|
+
const anchorSeat = anchorSeatId ? seatDetailsById.get(anchorSeatId) : null;
|
|
313
|
+
const candidates = Array.from(seatDetailsById.values()).filter(
|
|
314
|
+
(seat) => seat.categoryId === categoryId && seat.status === AVAILABLE_STATUS_ID && !selectedSet.has(seat.seatId)
|
|
315
|
+
);
|
|
316
|
+
if (candidates.length === 0) return;
|
|
317
|
+
let nextSeat = candidates[0];
|
|
318
|
+
if (anchorSeat) {
|
|
319
|
+
nextSeat = candidates.reduce((best, candidate) => {
|
|
320
|
+
const bestDistance = (best.x - anchorSeat.x) ** 2 + (best.y - anchorSeat.y) ** 2;
|
|
321
|
+
const candidateDistance = (candidate.x - anchorSeat.x) ** 2 + (candidate.y - anchorSeat.y) ** 2;
|
|
322
|
+
return candidateDistance < bestDistance ? candidate : best;
|
|
323
|
+
}, candidates[0]);
|
|
324
|
+
}
|
|
325
|
+
setSelection([...selectedSeatIdArray, nextSeat.seatId]);
|
|
326
|
+
},
|
|
327
|
+
[lastUserAnchorByCategory, seatDetailsById, selectedSeatIdArray, setSelection]
|
|
328
|
+
);
|
|
329
|
+
const handleRemoveLastInCategory = useCallback(
|
|
330
|
+
(categoryId) => {
|
|
331
|
+
const selectedInCategory = selectedSeatIdArray.filter(
|
|
332
|
+
(selectedSeatId) => seatDetailsById.get(selectedSeatId)?.categoryId === categoryId
|
|
333
|
+
);
|
|
334
|
+
const seatIdToRemove = selectedInCategory[selectedInCategory.length - 1];
|
|
335
|
+
if (!seatIdToRemove) return;
|
|
336
|
+
setSelection(selectedSeatIdArray.filter((selectedSeatId) => selectedSeatId !== seatIdToRemove));
|
|
337
|
+
},
|
|
338
|
+
[seatDetailsById, selectedSeatIdArray, setSelection]
|
|
339
|
+
);
|
|
340
|
+
const handleProceed = useCallback(() => {
|
|
341
|
+
if (cartPayload.seats.length === 0) return;
|
|
342
|
+
emitCartEvent({ type: "cart-proceed-clicked", payload: cartPayload });
|
|
343
|
+
}, [cartPayload, emitCartEvent]);
|
|
344
|
+
const cartWidth = viewportWidth > 1200 ? Math.floor(viewportWidth / 5) : viewportWidth;
|
|
345
|
+
return /* @__PURE__ */ jsxs("div", { style: { width: "100%", height: "100%", position: "relative" }, children: [
|
|
14
346
|
/* @__PURE__ */ jsx(
|
|
15
347
|
SeatmapCanvas,
|
|
16
348
|
{
|
|
17
|
-
onSeatClick,
|
|
18
|
-
onSeatHover
|
|
349
|
+
onSeatClick: handleSeatCanvasClick,
|
|
350
|
+
onSeatHover,
|
|
351
|
+
showSectionLabels: showLabels,
|
|
352
|
+
enableSeatHover: showLabels
|
|
19
353
|
}
|
|
20
354
|
),
|
|
21
|
-
/* @__PURE__ */
|
|
22
|
-
|
|
355
|
+
(showStatuses || showCategories) && /* @__PURE__ */ jsxs("aside", { "aria-label": "Seatmap legend", style: legendContainerStyle, children: [
|
|
356
|
+
showStatuses && /* @__PURE__ */ jsxs("section", { children: [
|
|
357
|
+
/* @__PURE__ */ jsx("p", { style: legendHeadingStyle, children: "Seat Status" }),
|
|
358
|
+
/* @__PURE__ */ jsx("ul", { style: legendListStyle, children: venue.seatStatuses.map((status) => /* @__PURE__ */ jsxs("li", { style: legendItemStyle, children: [
|
|
359
|
+
/* @__PURE__ */ jsx("span", { style: { ...legendSwatchStyle, background: status.color } }),
|
|
360
|
+
/* @__PURE__ */ jsx("span", { children: status.name })
|
|
361
|
+
] }, status.id)) })
|
|
362
|
+
] }),
|
|
363
|
+
showStatuses && showCategories && /* @__PURE__ */ jsx("div", { style: { height: 1, background: "rgba(123, 123, 165, 0.45)", margin: "8px 0" } }),
|
|
364
|
+
showCategories && /* @__PURE__ */ jsxs("section", { children: [
|
|
365
|
+
/* @__PURE__ */ jsx("p", { style: legendHeadingStyle, children: "Pricing" }),
|
|
366
|
+
/* @__PURE__ */ jsx("ul", { style: legendListStyle, children: venue.categories.map((category) => /* @__PURE__ */ jsxs("li", { style: legendItemStyle, children: [
|
|
367
|
+
/* @__PURE__ */ jsx("span", { style: { ...legendSwatchStyle, background: category.color } }),
|
|
368
|
+
/* @__PURE__ */ jsx("span", { children: category.name })
|
|
369
|
+
] }, category.id)) })
|
|
370
|
+
] })
|
|
371
|
+
] }),
|
|
372
|
+
!isCartOpen && /* @__PURE__ */ jsxs("button", { type: "button", onClick: () => setIsCartOpen(true), style: cartChipStyle, children: [
|
|
373
|
+
"Cart (",
|
|
374
|
+
totalSelectedSeats,
|
|
375
|
+
")"
|
|
376
|
+
] }),
|
|
377
|
+
isCartOpen && /* @__PURE__ */ jsxs("aside", { "aria-label": "Selected seats cart", style: { ...cartPopupStyle, width: cartWidth, maxWidth: "100%" }, children: [
|
|
378
|
+
/* @__PURE__ */ jsxs(
|
|
379
|
+
"div",
|
|
380
|
+
{
|
|
381
|
+
style: {
|
|
382
|
+
display: "flex",
|
|
383
|
+
alignItems: "center",
|
|
384
|
+
justifyContent: "space-between",
|
|
385
|
+
gap: 8,
|
|
386
|
+
padding: "10px 12px",
|
|
387
|
+
borderBottom: "1px solid rgba(110, 110, 150, 0.35)"
|
|
388
|
+
},
|
|
389
|
+
children: [
|
|
390
|
+
/* @__PURE__ */ jsx("strong", { style: { color: "#ececff", fontSize: 13, fontFamily: "system-ui" }, children: "Selected Seats Cart" }),
|
|
391
|
+
/* @__PURE__ */ jsx(
|
|
392
|
+
"button",
|
|
393
|
+
{
|
|
394
|
+
type: "button",
|
|
395
|
+
onClick: () => setIsCartOpen(false),
|
|
396
|
+
style: { ...cartIconButtonStyle, width: 22, height: 22 },
|
|
397
|
+
title: "Close cart",
|
|
398
|
+
"aria-label": "Close cart",
|
|
399
|
+
children: "x"
|
|
400
|
+
}
|
|
401
|
+
)
|
|
402
|
+
]
|
|
403
|
+
}
|
|
404
|
+
),
|
|
405
|
+
/* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 8, overflow: "auto", padding: 12, flex: 1 }, children: groupedSeatsList.length === 0 ? /* @__PURE__ */ jsx("div", { style: { color: "#9e9eb8", fontSize: 12, fontFamily: "system-ui" }, children: "No seats selected yet." }) : groupedSeatsList.map((group) => /* @__PURE__ */ jsxs(
|
|
406
|
+
"article",
|
|
407
|
+
{
|
|
408
|
+
style: {
|
|
409
|
+
background: "#242447",
|
|
410
|
+
border: "1px solid #3b3b61",
|
|
411
|
+
borderRadius: 8,
|
|
412
|
+
padding: 8,
|
|
413
|
+
display: "flex",
|
|
414
|
+
flexDirection: "column",
|
|
415
|
+
gap: 6
|
|
416
|
+
},
|
|
417
|
+
children: [
|
|
418
|
+
/* @__PURE__ */ jsx("div", { style: { color: "#ececff", fontSize: 12, fontFamily: "system-ui", fontWeight: 600 }, children: group.categoryName }),
|
|
419
|
+
/* @__PURE__ */ jsxs("div", { style: { color: "#adb0d2", fontSize: 11, fontFamily: "system-ui" }, children: [
|
|
420
|
+
group.seats.length,
|
|
421
|
+
" ticket",
|
|
422
|
+
group.seats.length === 1 ? "" : "s",
|
|
423
|
+
" - ",
|
|
424
|
+
formatMoney(group.unitPrice),
|
|
425
|
+
" each"
|
|
426
|
+
] }),
|
|
427
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8 }, children: [
|
|
428
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "inline-flex", alignItems: "center", gap: 6 }, children: [
|
|
429
|
+
/* @__PURE__ */ jsx(
|
|
430
|
+
"button",
|
|
431
|
+
{
|
|
432
|
+
type: "button",
|
|
433
|
+
style: cartIconButtonStyle,
|
|
434
|
+
onClick: () => handleRemoveLastInCategory(group.categoryId),
|
|
435
|
+
disabled: group.seats.length === 0,
|
|
436
|
+
"aria-label": `Remove one ${group.categoryName} seat`,
|
|
437
|
+
children: "-"
|
|
438
|
+
}
|
|
439
|
+
),
|
|
440
|
+
/* @__PURE__ */ jsx("span", { style: { minWidth: 20, textAlign: "center", color: "#ececff", fontSize: 12 }, children: group.seats.length }),
|
|
441
|
+
/* @__PURE__ */ jsx(
|
|
442
|
+
"button",
|
|
443
|
+
{
|
|
444
|
+
type: "button",
|
|
445
|
+
style: cartIconButtonStyle,
|
|
446
|
+
onClick: () => handleAddSeatInCategory(group.categoryId),
|
|
447
|
+
disabled: group.availableToAdd === 0,
|
|
448
|
+
"aria-label": `Add one ${group.categoryName} seat`,
|
|
449
|
+
children: "+"
|
|
450
|
+
}
|
|
451
|
+
)
|
|
452
|
+
] }),
|
|
453
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#ececff", fontSize: 12, fontFamily: "system-ui", fontWeight: 600 }, children: formatMoney(group.seats.reduce((sum, seat) => sum + seat.unitPrice, 0)) })
|
|
454
|
+
] }),
|
|
455
|
+
/* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: group.seats.map((seat) => /* @__PURE__ */ jsxs(
|
|
456
|
+
"div",
|
|
457
|
+
{
|
|
458
|
+
style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8 },
|
|
459
|
+
children: [
|
|
460
|
+
/* @__PURE__ */ jsxs("span", { style: { color: "#c8c8df", fontSize: 11, fontFamily: "system-ui" }, children: [
|
|
461
|
+
seat.sectionLabel ?? "Section",
|
|
462
|
+
" ",
|
|
463
|
+
seat.rowLabel ? `- ${seat.rowLabel}${seat.seatLabel}` : `- ${seat.seatLabel}`
|
|
464
|
+
] }),
|
|
465
|
+
/* @__PURE__ */ jsx(
|
|
466
|
+
"button",
|
|
467
|
+
{
|
|
468
|
+
type: "button",
|
|
469
|
+
style: cartIconButtonStyle,
|
|
470
|
+
onClick: () => removeSeatFromSelection(seat.seatId),
|
|
471
|
+
title: "Remove seat",
|
|
472
|
+
"aria-label": `Remove seat ${seat.seatLabel}`,
|
|
473
|
+
children: /* @__PURE__ */ jsx(TrashIcon, {})
|
|
474
|
+
}
|
|
475
|
+
)
|
|
476
|
+
]
|
|
477
|
+
},
|
|
478
|
+
seat.seatId
|
|
479
|
+
)) })
|
|
480
|
+
]
|
|
481
|
+
},
|
|
482
|
+
group.categoryId
|
|
483
|
+
)) }),
|
|
484
|
+
/* @__PURE__ */ jsxs(
|
|
485
|
+
"div",
|
|
486
|
+
{
|
|
487
|
+
style: {
|
|
488
|
+
borderTop: "1px solid rgba(110, 110, 150, 0.35)",
|
|
489
|
+
padding: 12,
|
|
490
|
+
display: "flex",
|
|
491
|
+
flexDirection: "column",
|
|
492
|
+
gap: 8
|
|
493
|
+
},
|
|
494
|
+
children: [
|
|
495
|
+
/* @__PURE__ */ jsxs("div", { style: { color: "#d4d4ee", fontSize: 12, fontFamily: "system-ui" }, children: [
|
|
496
|
+
totalSelectedSeats,
|
|
497
|
+
" seat",
|
|
498
|
+
totalSelectedSeats === 1 ? "" : "s",
|
|
499
|
+
" - Total ",
|
|
500
|
+
formatMoney(totalCost)
|
|
501
|
+
] }),
|
|
502
|
+
/* @__PURE__ */ jsx(
|
|
503
|
+
"button",
|
|
504
|
+
{
|
|
505
|
+
type: "button",
|
|
506
|
+
disabled: cartSeats.length === 0,
|
|
507
|
+
onClick: handleProceed,
|
|
508
|
+
style: {
|
|
509
|
+
border: "1px solid #2f7b44",
|
|
510
|
+
background: cartSeats.length === 0 ? "#30543d" : "#2f7b44",
|
|
511
|
+
color: "#ecfff2",
|
|
512
|
+
borderRadius: 8,
|
|
513
|
+
padding: "8px 12px",
|
|
514
|
+
fontSize: 13,
|
|
515
|
+
fontWeight: 600,
|
|
516
|
+
fontFamily: "system-ui",
|
|
517
|
+
cursor: cartSeats.length === 0 ? "not-allowed" : "pointer"
|
|
518
|
+
},
|
|
519
|
+
children: "Proceed"
|
|
520
|
+
}
|
|
521
|
+
)
|
|
522
|
+
]
|
|
523
|
+
}
|
|
524
|
+
)
|
|
525
|
+
] }),
|
|
526
|
+
showLabels && /* @__PURE__ */ jsx(TooltipOverlay, { renderTooltip })
|
|
527
|
+
] });
|
|
528
|
+
}
|
|
529
|
+
function SeatmapViewer({
|
|
530
|
+
venue,
|
|
531
|
+
onSeatClick,
|
|
532
|
+
onSeatHover,
|
|
533
|
+
onSelectionChange,
|
|
534
|
+
renderTooltip,
|
|
535
|
+
showLabels = true,
|
|
536
|
+
className,
|
|
537
|
+
onCartEvent
|
|
538
|
+
}) {
|
|
539
|
+
return /* @__PURE__ */ jsx(SeatmapProvider, { venue, children: /* @__PURE__ */ jsx("div", { className, style: { width: "100%", height: "100%", position: "relative" }, children: /* @__PURE__ */ jsx(
|
|
540
|
+
ViewerContent,
|
|
541
|
+
{
|
|
542
|
+
venue,
|
|
543
|
+
onSeatClick,
|
|
544
|
+
onSeatHover,
|
|
545
|
+
onSelectionChange,
|
|
546
|
+
renderTooltip,
|
|
547
|
+
showLabels,
|
|
548
|
+
onCartEvent
|
|
549
|
+
}
|
|
550
|
+
) }) });
|
|
23
551
|
}
|
|
24
552
|
|
|
25
553
|
export { SeatmapViewer };
|