iticket-seatingplan-dev 1.5.2 → 1.5.4

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.
@@ -0,0 +1,74 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { NZDollar } from "../utils";
3
+ import { Price } from "../utils/types";
4
+ import Flexi from "./Flexi";
5
+
6
+ export default function PriceButton({
7
+ price,
8
+ addToCart,
9
+ panMap,
10
+ }: {
11
+ price: Price;
12
+ addToCart: (amount: number) => void;
13
+ panMap: () => void;
14
+ }) {
15
+ const [flexiOpen, setFlexiOpen] = useState(false);
16
+ const flexiRef = useRef<HTMLDivElement | null>(null);
17
+ const focusRef = useRef<Element | null>(null);
18
+
19
+ const isFlexi = !!price.pMax && !!price.pMin && price.pMax > price.pMin;
20
+
21
+ useEffect(() => {
22
+ if (flexiOpen) {
23
+ focusRef.current = flexiRef.current?.querySelectorAll(
24
+ `button, [href], input, select, textarea,
25
+ [tabindex]:not([tabindex="-1"]),video`
26
+ )[0];
27
+
28
+ (focusRef.current as HTMLElement)?.focus();
29
+ }
30
+ }, [flexiOpen]);
31
+
32
+ return (
33
+ <div className="priceage-container">
34
+ <button
35
+ className={isFlexi ? "priceage flexi" : "priceage"}
36
+ onClick={() => {
37
+ if (isFlexi) {
38
+ setFlexiOpen((prev) => !prev);
39
+ } else {
40
+ addToCart(price.p);
41
+ }
42
+ }}
43
+ disabled={price.is_available === false}
44
+ >
45
+ <span className="price">
46
+ <span className="price-name">
47
+ <span>{price.paName}</span>
48
+ {isFlexi && <span className="flexi-badge">FLEXi</span>}
49
+ </span>
50
+ <span className="amount">
51
+ {isFlexi
52
+ ? `${NZDollar.format(price.pMin)} - ${NZDollar.format(
53
+ price.pMax
54
+ )}`
55
+ : NZDollar.format(price.p)}
56
+ </span>
57
+ </span>
58
+ {price.is_available === false && (
59
+ <span className="unavailable">Allocation exhausted</span>
60
+ )}
61
+ </button>
62
+ {isFlexi && (
63
+ <Flexi
64
+ price={price}
65
+ onClose={() => setFlexiOpen(false)}
66
+ onConfirm={addToCart}
67
+ buttonText="Add to cart"
68
+ isOpen={flexiOpen}
69
+ panMap={panMap}
70
+ />
71
+ )}
72
+ </div>
73
+ );
74
+ }
@@ -0,0 +1,178 @@
1
+ import { useState } from "react";
2
+ import { NZDollar } from "../utils";
3
+ import { Price, Seat } from "../utils/types";
4
+ import { closeIcon, flexiIcon } from "./assets/encodedSvgs";
5
+ import Flexi from "./Flexi";
6
+
7
+ interface SeatGroup {
8
+ seats: Seat[];
9
+ priceage?: Price;
10
+ }
11
+
12
+ const PriceSelect = ({
13
+ seatGroup,
14
+ seatPriceages,
15
+ updateSeatGroupPriceage,
16
+ }: {
17
+ seatGroup: SeatGroup;
18
+ seatPriceages: Price[];
19
+ updateSeatGroupPriceage: (price: Price) => void;
20
+ }) => {
21
+ const [flexiOpen, setFlexiOpen] = useState(false);
22
+
23
+ const isSelectedPriceFlexi =
24
+ !!seatGroup.priceage.pMax &&
25
+ !!seatGroup.priceage.pMin &&
26
+ seatGroup.priceage.pMax > seatGroup.priceage.pMin;
27
+
28
+ return (
29
+ <div className="price-select">
30
+ <select
31
+ onChange={(e) => {
32
+ const newPriceage = seatPriceages.find(
33
+ (p) => p.paId.toString() === e.target.value
34
+ );
35
+ updateSeatGroupPriceage(newPriceage);
36
+ }}
37
+ value={seatGroup.priceage.paId}
38
+ >
39
+ {seatPriceages.map((price) => (
40
+ <option
41
+ key={price.paId}
42
+ disabled={price.is_available === false}
43
+ value={price.paId}
44
+ >
45
+ {`${price.paName} ${NZDollar.format(
46
+ price.paId === seatGroup.priceage.paId
47
+ ? seatGroup.priceage.p
48
+ : price.p
49
+ )}${price.is_available === false ? " - Allocation exhausted" : ""}`}
50
+ </option>
51
+ ))}
52
+ </select>
53
+ {isSelectedPriceFlexi && (
54
+ <>
55
+ <button className="flexi-button" onClick={() => setFlexiOpen(true)}>
56
+ <img src={flexiIcon} height="18px" width="18px" />
57
+ <span>Edit FLEXi price</span>
58
+ </button>
59
+ {flexiOpen && (
60
+ <div className="flexi-popup" role="dialog" aria-modal="true">
61
+ <Flexi
62
+ price={seatGroup.priceage}
63
+ onClose={() => setFlexiOpen(false)}
64
+ onConfirm={(value) => {
65
+ const newPriceage = { ...seatGroup.priceage, p: value };
66
+ updateSeatGroupPriceage(newPriceage);
67
+ }}
68
+ buttonText="Confirm"
69
+ />
70
+ </div>
71
+ )}
72
+ </>
73
+ )}
74
+ </div>
75
+ );
76
+ };
77
+
78
+ interface Props {
79
+ cancelPriceSelect: () => void;
80
+ groupedSelectedSeats: {
81
+ [key: number]: SeatGroup;
82
+ };
83
+ pricing: Price[];
84
+ priceSectionIds?: number[];
85
+ batchAddTicketsToCart: (seats: { seat: Seat; priceage: Price }[]) => void;
86
+ }
87
+
88
+ export default function PricingPopup({
89
+ cancelPriceSelect,
90
+ groupedSelectedSeats,
91
+ pricing,
92
+ priceSectionIds,
93
+ batchAddTicketsToCart,
94
+ }: Props) {
95
+ const [seatsToBook, setSeatsToBook] = useState(groupedSelectedSeats);
96
+
97
+ return (
98
+ <>
99
+ <div className="pricing-popup-backdrop" onClick={cancelPriceSelect} />
100
+ <div className="pricing-popup" role="dialog" aria-modal="true">
101
+ <div className="popup-header">
102
+ <h3>Choose prices</h3>
103
+ <button
104
+ className="close-button"
105
+ aria-label="close popup"
106
+ onClick={cancelPriceSelect}
107
+ >
108
+ <img src={closeIcon} alt="close" height="20px" width="20px" />
109
+ </button>
110
+ </div>
111
+ <div className="seat-list">
112
+ {Object.entries(seatsToBook).map(([id, seatGroup]) => {
113
+ const seatPriceages = pricing.filter(
114
+ (price) =>
115
+ (!priceSectionIds || priceSectionIds.includes(price.psId)) &&
116
+ price.psId === parseInt(id)
117
+ );
118
+
119
+ return (
120
+ <div key={id} className="seat-group">
121
+ <div className="seat-group-header">
122
+ <h4>{seatGroup.seats[0].psName}</h4>
123
+ <p>{`${seatGroup.seats.length} seat${
124
+ seatGroup.seats.length !== 1 ? "s" : ""
125
+ }`}</p>
126
+ </div>
127
+ <p className="seat-names">
128
+ {seatGroup.seats
129
+ .map((s) => `${s.r}-${s.c}`)
130
+ .sort()
131
+ .join(", ")}
132
+ </p>
133
+ {seatPriceages.filter((price) => price.is_available !== false)
134
+ .length > 0 ? (
135
+ <PriceSelect
136
+ seatGroup={seatGroup}
137
+ seatPriceages={seatPriceages}
138
+ updateSeatGroupPriceage={(newPriceage) => {
139
+ const newSeats = { ...seatsToBook };
140
+ newSeats[id].priceage = newPriceage;
141
+ setSeatsToBook(newSeats);
142
+ }}
143
+ />
144
+ ) : (
145
+ <p className="no-prices-available">
146
+ Oops, looks like there are no prices available for this
147
+ section.
148
+ </p>
149
+ )}
150
+ </div>
151
+ );
152
+ })}
153
+ </div>
154
+ <div className="buttons">
155
+ <button className="cancel" onClick={cancelPriceSelect}>
156
+ Cancel
157
+ </button>
158
+ <button
159
+ className="add-cart"
160
+ onClick={() => {
161
+ batchAddTicketsToCart(
162
+ Object.values(seatsToBook)
163
+ .filter((v) => !!v.priceage)
164
+ .flatMap((s) =>
165
+ s.seats.map((seat) => ({ seat, priceage: s.priceage }))
166
+ )
167
+ );
168
+ cancelPriceSelect();
169
+ }}
170
+ disabled={Object.values(seatsToBook).every((v) => !v.priceage)}
171
+ >
172
+ Add to cart
173
+ </button>
174
+ </div>
175
+ </div>
176
+ </>
177
+ );
178
+ }