@eventlook/sdk 1.4.49 → 1.4.51
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/cjs/form/PaymentOverviewBox.js +0 -6
- package/dist/cjs/form/PaymentOverviewBox.js.map +1 -1
- package/dist/cjs/form/TicketForm.js +1 -1
- package/dist/cjs/form/TicketForm.js.map +1 -1
- package/dist/cjs/form/product/ProductCard.js +3 -2
- package/dist/cjs/form/product/ProductCard.js.map +1 -1
- package/dist/cjs/form/product/ProductVariantsDialog.js +23 -8
- package/dist/cjs/form/product/ProductVariantsDialog.js.map +1 -1
- package/dist/cjs/form/services/index.js +1 -18
- package/dist/cjs/form/services/index.js.map +1 -1
- package/dist/cjs/form/tickets/ReleaseWithMerchandise.js +30 -7
- package/dist/cjs/form/tickets/ReleaseWithMerchandise.js.map +1 -1
- package/dist/cjs/form/tickets/TicketSelection.js +37 -14
- package/dist/cjs/form/tickets/TicketSelection.js.map +1 -1
- package/dist/cjs/form/tickets/TicketSelectionMap.js +7 -1
- package/dist/cjs/form/tickets/TicketSelectionMap.js.map +1 -1
- package/dist/cjs/form/tickets/TicketSelectionMobile.js +17 -7
- package/dist/cjs/form/tickets/TicketSelectionMobile.js.map +1 -1
- package/dist/cjs/form/tickets/TicketWithMerchandiseSelection.js +30 -20
- package/dist/cjs/form/tickets/TicketWithMerchandiseSelection.js.map +1 -1
- package/dist/cjs/utils/data/ticket.js +6 -0
- package/dist/cjs/utils/data/ticket.js.map +1 -0
- package/dist/esm/form/PaymentOverviewBox.js +0 -6
- package/dist/esm/form/PaymentOverviewBox.js.map +1 -1
- package/dist/esm/form/TicketForm.js +1 -1
- package/dist/esm/form/TicketForm.js.map +1 -1
- package/dist/esm/form/product/ProductCard.js +3 -2
- package/dist/esm/form/product/ProductCard.js.map +1 -1
- package/dist/esm/form/product/ProductVariantsDialog.js +23 -8
- package/dist/esm/form/product/ProductVariantsDialog.js.map +1 -1
- package/dist/esm/form/services/index.js +2 -19
- package/dist/esm/form/services/index.js.map +1 -1
- package/dist/esm/form/tickets/ReleaseWithMerchandise.js +30 -7
- package/dist/esm/form/tickets/ReleaseWithMerchandise.js.map +1 -1
- package/dist/esm/form/tickets/TicketSelection.js +38 -15
- package/dist/esm/form/tickets/TicketSelection.js.map +1 -1
- package/dist/esm/form/tickets/TicketSelectionMap.js +7 -1
- package/dist/esm/form/tickets/TicketSelectionMap.js.map +1 -1
- package/dist/esm/form/tickets/TicketSelectionMobile.js +17 -7
- package/dist/esm/form/tickets/TicketSelectionMobile.js.map +1 -1
- package/dist/esm/form/tickets/TicketWithMerchandiseSelection.js +30 -20
- package/dist/esm/form/tickets/TicketWithMerchandiseSelection.js.map +1 -1
- package/dist/esm/utils/data/ticket.js +4 -0
- package/dist/esm/utils/data/ticket.js.map +1 -0
- package/package.json +1 -1
- package/src/form/PaymentOverviewBox.tsx +11 -11
- package/src/form/TicketForm.tsx +8 -6
- package/src/form/product/ProductCard.tsx +5 -2
- package/src/form/product/ProductVariantsDialog.tsx +29 -6
- package/src/form/services/index.tsx +36 -36
- package/src/form/tickets/ReleaseWithMerchandise.tsx +39 -8
- package/src/form/tickets/TicketSelection.tsx +50 -17
- package/src/form/tickets/TicketSelectionMap.tsx +9 -1
- package/src/form/tickets/TicketSelectionMobile.tsx +77 -67
- package/src/form/tickets/TicketWithMerchandiseSelection.tsx +49 -31
- package/src/utils/data/ticket.ts +1 -0
|
@@ -22,6 +22,7 @@ import { ITicketForm, ITicketFormTicket } from '@utils/types/ticket.type';
|
|
|
22
22
|
import { getSelectedQuantityByVariant } from '@utils/product';
|
|
23
23
|
import useGlobal from '@hooks/useGlobal.ts';
|
|
24
24
|
import { fCurrency } from '@utils/formatNumber';
|
|
25
|
+
import { MAX_TICKETS_PER_ORDER } from '@utils/data/ticket';
|
|
25
26
|
|
|
26
27
|
interface Props {
|
|
27
28
|
eventProduct: IEventProduct;
|
|
@@ -100,7 +101,7 @@ const ProductCard: React.FC<Props> = ({ eventProduct, eventId, isOnlyMerchandise
|
|
|
100
101
|
|
|
101
102
|
const onChangeSimpleProductQuantity = (nextQuantity: number) => {
|
|
102
103
|
if (!simpleVariant) return;
|
|
103
|
-
if (nextQuantity < 0 || nextQuantity >
|
|
104
|
+
if (nextQuantity < 0 || nextQuantity > MAX_TICKETS_PER_ORDER) return;
|
|
104
105
|
if (
|
|
105
106
|
nextQuantity > simpleProductQuantity &&
|
|
106
107
|
nextQuantity - simpleProductQuantity > simpleProductMaxAvailable
|
|
@@ -153,7 +154,9 @@ const ProductCard: React.FC<Props> = ({ eventProduct, eventId, isOnlyMerchandise
|
|
|
153
154
|
</Box>
|
|
154
155
|
<IconButton
|
|
155
156
|
onClick={() => onChangeSimpleProductQuantity(simpleProductQuantity + 1)}
|
|
156
|
-
disabled={
|
|
157
|
+
disabled={
|
|
158
|
+
simpleProductQuantity >= MAX_TICKETS_PER_ORDER || simpleProductMaxAvailable <= 0
|
|
159
|
+
}
|
|
157
160
|
sx={{
|
|
158
161
|
flex: '1 1 0',
|
|
159
162
|
height: 36,
|
|
@@ -15,6 +15,7 @@ import { useWatch } from 'react-hook-form';
|
|
|
15
15
|
import { ITicketFormTicket } from '@utils/types/ticket.type';
|
|
16
16
|
import { IEventProductForm } from '@utils/types/product.type';
|
|
17
17
|
import { fCurrency } from '@utils/formatNumber';
|
|
18
|
+
import { MAX_TICKETS_PER_ORDER } from '@utils/data/ticket';
|
|
18
19
|
import useGlobal from '@hooks/useGlobal.ts';
|
|
19
20
|
|
|
20
21
|
interface Props {
|
|
@@ -26,6 +27,7 @@ interface Props {
|
|
|
26
27
|
selectedQuantityByVariant?: Record<number, number>;
|
|
27
28
|
isOnlyMerchandise?: boolean;
|
|
28
29
|
canAddOnlyOneAtATime?: boolean;
|
|
30
|
+
maxSelectableQuantity?: number;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
const ProductVariantsDialog: React.FC<Props> = ({
|
|
@@ -37,6 +39,7 @@ const ProductVariantsDialog: React.FC<Props> = ({
|
|
|
37
39
|
isOnlyMerchandise,
|
|
38
40
|
eventId,
|
|
39
41
|
canAddOnlyOneAtATime,
|
|
42
|
+
maxSelectableQuantity,
|
|
40
43
|
}) => {
|
|
41
44
|
const { t, lang, options } = useGlobal();
|
|
42
45
|
const { showSnackbar } = useGlobal();
|
|
@@ -53,8 +56,14 @@ const ProductVariantsDialog: React.FC<Props> = ({
|
|
|
53
56
|
}, [openDialog]);
|
|
54
57
|
|
|
55
58
|
useEffect(() => {
|
|
56
|
-
if (openDialog) {
|
|
57
|
-
setDraftQuantities(
|
|
59
|
+
if (openDialog && canAddOnlyOneAtATime) {
|
|
60
|
+
setDraftQuantities({});
|
|
61
|
+
}
|
|
62
|
+
}, [openDialog, canAddOnlyOneAtATime]);
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (openDialog && !canAddOnlyOneAtATime) {
|
|
66
|
+
setDraftQuantities(selectedQuantityByVariant ?? {});
|
|
58
67
|
}
|
|
59
68
|
}, [openDialog, selectedQuantityByVariant, canAddOnlyOneAtATime]);
|
|
60
69
|
|
|
@@ -126,11 +135,18 @@ const ProductVariantsDialog: React.FC<Props> = ({
|
|
|
126
135
|
const current = prev[variant.id] ?? 0;
|
|
127
136
|
const next = current + 1;
|
|
128
137
|
const maxAvailable = getMaxAvailable(variant);
|
|
129
|
-
|
|
138
|
+
|
|
130
139
|
if (canAddOnlyOneAtATime) {
|
|
131
|
-
|
|
140
|
+
if (maxAvailable < 1) return prev;
|
|
132
141
|
return { [variant.id]: 1 };
|
|
133
142
|
}
|
|
143
|
+
|
|
144
|
+
const maxSelectable =
|
|
145
|
+
maxSelectableQuantity === undefined
|
|
146
|
+
? MAX_TICKETS_PER_ORDER
|
|
147
|
+
: Math.max(0, Math.min(maxSelectableQuantity, MAX_TICKETS_PER_ORDER));
|
|
148
|
+
|
|
149
|
+
if (next > maxSelectable || next > maxAvailable) return prev;
|
|
134
150
|
return { ...prev, [variant.id]: next };
|
|
135
151
|
});
|
|
136
152
|
};
|
|
@@ -248,7 +264,14 @@ const ProductVariantsDialog: React.FC<Props> = ({
|
|
|
248
264
|
{eventProduct.eventProductVariants.map((variant) => {
|
|
249
265
|
const draftQty = draftQuantities[variant.id] ?? 0;
|
|
250
266
|
const maxAvailable = getMaxAvailable(variant);
|
|
251
|
-
const
|
|
267
|
+
const maxSelectable =
|
|
268
|
+
maxSelectableQuantity === undefined
|
|
269
|
+
? MAX_TICKETS_PER_ORDER
|
|
270
|
+
: Math.max(0, Math.min(maxSelectableQuantity, MAX_TICKETS_PER_ORDER));
|
|
271
|
+
const isAddDisabled =
|
|
272
|
+
isVariantDisabled(variant) ||
|
|
273
|
+
maxAvailable <= 0 ||
|
|
274
|
+
(!canAddOnlyOneAtATime && maxSelectable <= 0);
|
|
252
275
|
const isAnotherSelected =
|
|
253
276
|
canAddOnlyOneAtATime &&
|
|
254
277
|
Object.keys(draftQuantities).length > 0 &&
|
|
@@ -330,7 +353,7 @@ const ProductVariantsDialog: React.FC<Props> = ({
|
|
|
330
353
|
variant="contained"
|
|
331
354
|
onClick={() => handleAddQuantity(variant)}
|
|
332
355
|
aria-label={t('components.product_variant_dialog.increase_quantity')}
|
|
333
|
-
disabled={draftQty >=
|
|
356
|
+
disabled={draftQty >= maxSelectable || draftQty >= maxAvailable}
|
|
334
357
|
sx={{ minWidth: 0, height: 36, borderRadius: 1, fontWeight: 700 }}
|
|
335
358
|
>
|
|
336
359
|
+
|
|
@@ -138,18 +138,18 @@ const Services: React.FC<Props> = ({ event }) => {
|
|
|
138
138
|
|
|
139
139
|
return (
|
|
140
140
|
<Stack spacing={1}>
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
141
|
+
{/*<BorderedCheckbox*/}
|
|
142
|
+
{/* name="ticketInsurance"*/}
|
|
143
|
+
{/* disabled={false}*/}
|
|
144
|
+
{/* title={t('event.tickets.insurance.label')}*/}
|
|
145
|
+
{/* price={*/}
|
|
146
|
+
{/* <>*/}
|
|
147
|
+
{/* + {fCurrency(ticketInsurancePricePerUnit, lang, event.currency)} /{' '}*/}
|
|
148
|
+
{/* {t('event.tickets.insurance.per_ticket')}*/}
|
|
149
|
+
{/* </>*/}
|
|
150
|
+
{/* }*/}
|
|
151
|
+
{/* onChange={handleServiceChange}*/}
|
|
152
|
+
{/*/>*/}
|
|
153
153
|
|
|
154
154
|
<BorderedCheckbox
|
|
155
155
|
name="smsNotification"
|
|
@@ -199,30 +199,30 @@ const Services: React.FC<Props> = ({ event }) => {
|
|
|
199
199
|
</DialogTitle>
|
|
200
200
|
<DialogContent sx={{ px: { xs: 3, sm: 4 }, pb: { xs: 4, sm: 5 } }}>
|
|
201
201
|
<Stack spacing={3}>
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
202
|
+
{/*<Box>*/}
|
|
203
|
+
{/* <Typography variant="subtitle2" fontWeight={700} gutterBottom>*/}
|
|
204
|
+
{/* {t('event.tickets.insurance.label')}*/}
|
|
205
|
+
{/* </Typography>*/}
|
|
206
|
+
{/* <Typography variant="body2" paragraph>*/}
|
|
207
|
+
{/* {t('event.tickets.insurance.modal.description')}*/}
|
|
208
|
+
{/* </Typography>*/}
|
|
209
|
+
{/* <Typography variant="body2" paragraph>*/}
|
|
210
|
+
{/* {t('event.tickets.insurance.modal.coverage')}*/}
|
|
211
|
+
{/* </Typography>*/}
|
|
212
|
+
{/* <BorderedCheckbox*/}
|
|
213
|
+
{/* name="ticketInsurance"*/}
|
|
214
|
+
{/* disabled={false}*/}
|
|
215
|
+
{/* title={t('event.tickets.insurance.label')}*/}
|
|
216
|
+
{/* price={*/}
|
|
217
|
+
{/* <>*/}
|
|
218
|
+
{/* + {fCurrency(ticketInsurancePricePerUnit, lang, event.currency)} /{' '}*/}
|
|
219
|
+
{/* {t('event.tickets.insurance.per_ticket')}*/}
|
|
220
|
+
{/* </>*/}
|
|
221
|
+
{/* }*/}
|
|
222
|
+
{/* onChange={handleServiceChange}*/}
|
|
223
|
+
{/* />*/}
|
|
224
|
+
{/*</Box>*/}
|
|
225
|
+
{/*<Divider />*/}
|
|
226
226
|
<Box>
|
|
227
227
|
<Typography variant="subtitle2" fontWeight={700} gutterBottom>
|
|
228
228
|
{t('event.tickets.sms_notification.label')}
|
|
@@ -8,6 +8,7 @@ import { useFormContext, useWatch } from 'react-hook-form';
|
|
|
8
8
|
import { IEventProductForm } from '@utils/types/product.type';
|
|
9
9
|
import { fCurrency } from '@utils/formatNumber';
|
|
10
10
|
import { Currencies } from '@utils/data/currency';
|
|
11
|
+
import { MAX_TICKETS_PER_ORDER } from '@utils/data/ticket';
|
|
11
12
|
import { getSelectedQuantityByVariant } from '@utils/product';
|
|
12
13
|
import ReleaseExtraFields from '@form/extra-field/ReleaseExtraFields';
|
|
13
14
|
import ReleaseDescription from '@form/tickets/ReleaseDescription';
|
|
@@ -43,10 +44,19 @@ const ReleaseWithMerchandise: React.FC<Props> = ({
|
|
|
43
44
|
const getSelectedQuantity = (id: number) =>
|
|
44
45
|
tickets.find((ticket) => ticket.releaseId === id)?.quantity || 0;
|
|
45
46
|
|
|
47
|
+
const countSelectedTickets = () => {
|
|
48
|
+
let count = 0;
|
|
49
|
+
for (const ticket of tickets) {
|
|
50
|
+
count += Number(ticket.quantity || 0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return count;
|
|
54
|
+
};
|
|
55
|
+
|
|
46
56
|
const getAvailableTicketsForRelease = (release: ITicketFormTicket): number => {
|
|
47
57
|
const selectedRelease = activeReleases?.find((item) => item.id === release.releaseId);
|
|
48
58
|
const availableQuantity = selectedRelease ? selectedRelease.availableTickets : 0;
|
|
49
|
-
return availableQuantity >
|
|
59
|
+
return availableQuantity > MAX_TICKETS_PER_ORDER ? MAX_TICKETS_PER_ORDER : availableQuantity;
|
|
50
60
|
};
|
|
51
61
|
|
|
52
62
|
const isMaxQuantity = (releaseId: number) => {
|
|
@@ -61,7 +71,17 @@ const ReleaseWithMerchandise: React.FC<Props> = ({
|
|
|
61
71
|
: productsToAdd
|
|
62
72
|
? [productsToAdd]
|
|
63
73
|
: [];
|
|
64
|
-
const
|
|
74
|
+
const requestedQuantity = normalizedProducts.length ? normalizedProducts.length : 1;
|
|
75
|
+
const releaseMaxQuantity = Math.min(release.availableTickets || 0, MAX_TICKETS_PER_ORDER);
|
|
76
|
+
const remainingOrderCapacity = Math.max(0, MAX_TICKETS_PER_ORDER - countSelectedTickets());
|
|
77
|
+
const quantity = Math.min(requestedQuantity, releaseMaxQuantity, remainingOrderCapacity);
|
|
78
|
+
|
|
79
|
+
if (quantity <= 0) {
|
|
80
|
+
setOpenVariantDialog(null);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const selectedProducts = normalizedProducts.slice(0, quantity);
|
|
65
85
|
const extraFields = release.extraFields?.length
|
|
66
86
|
? Array.from({ length: quantity }, () =>
|
|
67
87
|
release.extraFields!.map((field) => ({
|
|
@@ -78,7 +98,7 @@ const ReleaseWithMerchandise: React.FC<Props> = ({
|
|
|
78
98
|
quantity,
|
|
79
99
|
itemName: '',
|
|
80
100
|
price: 0,
|
|
81
|
-
products:
|
|
101
|
+
products: selectedProducts,
|
|
82
102
|
extraFields,
|
|
83
103
|
},
|
|
84
104
|
]);
|
|
@@ -95,9 +115,10 @@ const ReleaseWithMerchandise: React.FC<Props> = ({
|
|
|
95
115
|
if (addedRelease) {
|
|
96
116
|
const increment = normalizedProducts.length ? normalizedProducts.length : 1;
|
|
97
117
|
const maxQuantity = getAvailableTicketsForRelease(addedRelease);
|
|
118
|
+
const remainingOrderCapacity = Math.max(0, MAX_TICKETS_PER_ORDER - countSelectedTickets());
|
|
98
119
|
const availableIncrement = Math.max(
|
|
99
120
|
0,
|
|
100
|
-
Math.min(increment, maxQuantity - Number(addedRelease.quantity))
|
|
121
|
+
Math.min(increment, maxQuantity - Number(addedRelease.quantity), remainingOrderCapacity)
|
|
101
122
|
);
|
|
102
123
|
if (availableIncrement === 0) return;
|
|
103
124
|
|
|
@@ -158,6 +179,13 @@ const ReleaseWithMerchandise: React.FC<Props> = ({
|
|
|
158
179
|
}
|
|
159
180
|
}, [tickets, release.id, setValue]);
|
|
160
181
|
|
|
182
|
+
const remainingOrderCapacity = Math.max(0, MAX_TICKETS_PER_ORDER - countSelectedTickets());
|
|
183
|
+
const nextRelease = activeReleases?.find(
|
|
184
|
+
(item) =>
|
|
185
|
+
item.releaseCategoryName === release.releaseCategoryName && item.order === release.order + 1
|
|
186
|
+
);
|
|
187
|
+
const hasSelectedNextRelease = !!nextRelease && getSelectedQuantity(nextRelease.id) > 0;
|
|
188
|
+
|
|
161
189
|
return (
|
|
162
190
|
<Box
|
|
163
191
|
sx={{
|
|
@@ -173,7 +201,7 @@ const ReleaseWithMerchandise: React.FC<Props> = ({
|
|
|
173
201
|
<Stack spacing={0}>
|
|
174
202
|
<Box>
|
|
175
203
|
<Typography variant="subtitle2" fontWeight={700}>
|
|
176
|
-
{getReleaseTitle(release)}
|
|
204
|
+
{getReleaseTitle(release)} - {release.name}
|
|
177
205
|
</Typography>
|
|
178
206
|
</Box>
|
|
179
207
|
|
|
@@ -193,9 +221,11 @@ const ReleaseWithMerchandise: React.FC<Props> = ({
|
|
|
193
221
|
|
|
194
222
|
<TicketQuantityControl
|
|
195
223
|
quantity={getSelectedQuantity(release.id)}
|
|
196
|
-
isDisabled={release.locked}
|
|
197
|
-
canAddFirst={!release.locked}
|
|
198
|
-
canAddMore={
|
|
224
|
+
isDisabled={release.locked || hasSelectedNextRelease}
|
|
225
|
+
canAddFirst={!release.locked && !hasSelectedNextRelease && remainingOrderCapacity > 0}
|
|
226
|
+
canAddMore={
|
|
227
|
+
!isMaxQuantity(release.id) && !hasSelectedNextRelease && remainingOrderCapacity > 0
|
|
228
|
+
}
|
|
199
229
|
addLabel={t('add')}
|
|
200
230
|
onDecrement={() => decreaseQuantity()}
|
|
201
231
|
onIncrement={() =>
|
|
@@ -229,6 +259,7 @@ const ReleaseWithMerchandise: React.FC<Props> = ({
|
|
|
229
259
|
selectedQuantityByVariant={getSelectedQuantityByVariant(products, tickets)}
|
|
230
260
|
eventId={eventId}
|
|
231
261
|
canAddOnlyOneAtATime
|
|
262
|
+
maxSelectableQuantity={remainingOrderCapacity}
|
|
232
263
|
/>
|
|
233
264
|
)}
|
|
234
265
|
</Stack>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect, useRef
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
2
|
import { useFormContext, useWatch } from 'react-hook-form';
|
|
3
3
|
import { Box, Divider, Stack, Typography } from '@mui/material';
|
|
4
4
|
import { ITicketForm, ITicketFormTicket } from '@utils/types/ticket.type';
|
|
@@ -9,6 +9,7 @@ import { IEvent } from '@utils/types/event.type';
|
|
|
9
9
|
import useResponsive from '@hooks/useResponsive';
|
|
10
10
|
import ReleaseExtraFields from '@form/extra-field/ReleaseExtraFields';
|
|
11
11
|
import { EventType } from '@utils/data/event';
|
|
12
|
+
import { MAX_TICKETS_PER_ORDER } from '@utils/data/ticket';
|
|
12
13
|
import useGlobal from '@hooks/useGlobal.ts';
|
|
13
14
|
import TicketSelectionMobile from './TicketSelectionMobile';
|
|
14
15
|
|
|
@@ -25,7 +26,6 @@ const TicketSelection: React.FC<Props> = ({ event }) => {
|
|
|
25
26
|
defaultValue: [],
|
|
26
27
|
}) as ITicketFormTicket[];
|
|
27
28
|
const eventTimeslotId = watch('eventTimeslotId');
|
|
28
|
-
const [soldOutReleaseCategoryNames, setSoldOutReleaseCategoryNames] = useState<string[]>([]);
|
|
29
29
|
const isProcessingRef = useRef(false);
|
|
30
30
|
const { data: activeReleases, mutate } = useEventActiveReleases(
|
|
31
31
|
event.id,
|
|
@@ -68,7 +68,7 @@ const TicketSelection: React.FC<Props> = ({ event }) => {
|
|
|
68
68
|
// const getAvailableTicketsForRelease = (release: ITicketFormTicket): number => {
|
|
69
69
|
// const selectedRelease = activeReleases?.find((item) => item.id === release.releaseId);
|
|
70
70
|
// const availableQuantity = selectedRelease ? selectedRelease.availableTickets : 0;
|
|
71
|
-
// return availableQuantity >
|
|
71
|
+
// return availableQuantity > MAX_TICKETS_PER_ORDER ? MAX_TICKETS_PER_ORDER : availableQuantity;
|
|
72
72
|
// };
|
|
73
73
|
|
|
74
74
|
const countReleaseCategories = (): number => {
|
|
@@ -83,7 +83,15 @@ const TicketSelection: React.FC<Props> = ({ event }) => {
|
|
|
83
83
|
item2.releaseCategoryName === item.releaseCategoryName && item2.order === item.order + 1
|
|
84
84
|
);
|
|
85
85
|
const selected = tickets.find((ticket) => ticket.releaseId === item.id);
|
|
86
|
-
|
|
86
|
+
const maxSelectable = Math.min(item.availableTickets || 0, MAX_TICKETS_PER_ORDER);
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
!!nextRelease &&
|
|
90
|
+
item.locked &&
|
|
91
|
+
!!selected &&
|
|
92
|
+
Number(selected.quantity) >= maxSelectable &&
|
|
93
|
+
index + 1 == tickets.length
|
|
94
|
+
);
|
|
87
95
|
});
|
|
88
96
|
return lockedSelectedReleases && lockedSelectedReleases.includes(true);
|
|
89
97
|
};
|
|
@@ -102,8 +110,8 @@ const TicketSelection: React.FC<Props> = ({ event }) => {
|
|
|
102
110
|
const isQuantityDisabled = (value: number, releaseId: number | '') => {
|
|
103
111
|
const releaseSelected = tickets.find((item) => item.releaseId === releaseId);
|
|
104
112
|
return releaseSelected && releaseSelected.quantity
|
|
105
|
-
? countSelectedTickets() + value - releaseSelected.quantity >
|
|
106
|
-
: countSelectedTickets() + value >
|
|
113
|
+
? countSelectedTickets() + value - releaseSelected.quantity > MAX_TICKETS_PER_ORDER
|
|
114
|
+
: countSelectedTickets() + value > MAX_TICKETS_PER_ORDER;
|
|
107
115
|
};
|
|
108
116
|
|
|
109
117
|
const removeTicket = (indexToRemove: number) => {
|
|
@@ -113,21 +121,47 @@ const TicketSelection: React.FC<Props> = ({ event }) => {
|
|
|
113
121
|
|
|
114
122
|
const selectedTickets = async () => {
|
|
115
123
|
const releases = await mutate();
|
|
124
|
+
const currentReleases = releases || activeReleases || [];
|
|
116
125
|
const allFilled = tickets.filter((item) => !item.releaseId || !item.quantity);
|
|
117
126
|
|
|
118
|
-
const soldOutReleaseCategories =
|
|
127
|
+
const soldOutReleaseCategories = currentReleases.filter((release) =>
|
|
119
128
|
tickets.find(
|
|
120
129
|
(ticket) =>
|
|
121
130
|
release.id === ticket.releaseId &&
|
|
122
|
-
ticket.quantity
|
|
123
|
-
release.availableTickets !== 10
|
|
131
|
+
Number(ticket.quantity) >= Math.min(release.availableTickets || 0, MAX_TICKETS_PER_ORDER)
|
|
124
132
|
)
|
|
125
133
|
);
|
|
126
|
-
setSoldOutReleaseCategoryNames(
|
|
127
|
-
soldOutReleaseCategories?.map((item) => item.releaseCategoryName) || []
|
|
128
|
-
);
|
|
129
134
|
|
|
130
|
-
|
|
135
|
+
if (currentReleases.length) {
|
|
136
|
+
let hasChanges = false;
|
|
137
|
+
const updatedReleases = currentReleases.map((release) => {
|
|
138
|
+
const previousRelease = currentReleases.find(
|
|
139
|
+
(item) =>
|
|
140
|
+
item.releaseCategoryName === release.releaseCategoryName &&
|
|
141
|
+
item.order === release.order - 1
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
if (!release.locked || !previousRelease) return release;
|
|
145
|
+
|
|
146
|
+
const previousTicket = tickets.find((ticket) => ticket.releaseId === previousRelease.id);
|
|
147
|
+
const previousMaxSelectable = Math.min(
|
|
148
|
+
previousRelease.availableTickets || 0,
|
|
149
|
+
MAX_TICKETS_PER_ORDER
|
|
150
|
+
);
|
|
151
|
+
const shouldUnlock = Number(previousTicket?.quantity || 0) >= previousMaxSelectable;
|
|
152
|
+
|
|
153
|
+
if (!shouldUnlock) return release;
|
|
154
|
+
|
|
155
|
+
hasChanges = true;
|
|
156
|
+
return { ...release, locked: false };
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
if (hasChanges) {
|
|
160
|
+
await mutate(updatedReleases, false);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const hasSelectableRelease = currentReleases.some(
|
|
131
165
|
(release) => !isReleaseSelected(release.id) && !release.locked
|
|
132
166
|
);
|
|
133
167
|
|
|
@@ -136,9 +170,9 @@ const TicketSelection: React.FC<Props> = ({ event }) => {
|
|
|
136
170
|
selectedReleaseIsSoldOut(releases) &&
|
|
137
171
|
tickets.length < soldOutReleaseCategories.length + countUnlockedReleases() &&
|
|
138
172
|
!allFilled.length) ||
|
|
139
|
-
(
|
|
140
|
-
soldOutReleaseCategories
|
|
141
|
-
|
|
173
|
+
(currentReleases.length &&
|
|
174
|
+
soldOutReleaseCategories.length &&
|
|
175
|
+
currentReleases.length > tickets.length &&
|
|
142
176
|
tickets.length < soldOutReleaseCategories.length + countUnlockedReleases() &&
|
|
143
177
|
!allFilled.length) ||
|
|
144
178
|
(tickets.length < countReleaseCategories() && !allFilled.length);
|
|
@@ -184,7 +218,6 @@ const TicketSelection: React.FC<Props> = ({ event }) => {
|
|
|
184
218
|
event={event}
|
|
185
219
|
activeReleases={activeReleases}
|
|
186
220
|
showLoading={showLoading}
|
|
187
|
-
soldOutReleaseCategoryNames={soldOutReleaseCategoryNames}
|
|
188
221
|
tickets={tickets}
|
|
189
222
|
isQuantityDisabled={isQuantityDisabled}
|
|
190
223
|
setValue={setValue as (name: string, value: any) => void}
|
|
@@ -5,6 +5,7 @@ import { Button } from '@mui/material';
|
|
|
5
5
|
import { iframe, TicketSelection } from '@seat-picker/seat-picker-sdk';
|
|
6
6
|
import { useFormContext } from 'react-hook-form';
|
|
7
7
|
import { ITicketForm, ITicketFormTicket, ITicketLocation } from '@utils/types/ticket.type.ts';
|
|
8
|
+
import { MAX_TICKETS_PER_ORDER } from '@utils/data/ticket';
|
|
8
9
|
import Iconify from '@components/iconify/Iconify';
|
|
9
10
|
|
|
10
11
|
interface Props {
|
|
@@ -43,11 +44,18 @@ const TicketSelectionMap: React.FC<Props> = ({ event }) => {
|
|
|
43
44
|
[] as { quantity: number; seat: ITicketLocation; ticket: any }[]
|
|
44
45
|
);
|
|
45
46
|
|
|
47
|
+
let remainingTicketCapacity = MAX_TICKETS_PER_ORDER;
|
|
48
|
+
|
|
46
49
|
for (const groupedSeat of groupedSeatsByZone) {
|
|
50
|
+
if (remainingTicketCapacity <= 0) break;
|
|
51
|
+
|
|
52
|
+
const quantity = Math.min(groupedSeat.quantity, remainingTicketCapacity);
|
|
53
|
+
remainingTicketCapacity -= quantity;
|
|
54
|
+
|
|
47
55
|
tickets.push({
|
|
48
56
|
releaseId: groupedSeat.ticket.id,
|
|
49
57
|
price: groupedSeat.ticket.price,
|
|
50
|
-
quantity
|
|
58
|
+
quantity,
|
|
51
59
|
itemName: `${groupedSeat.ticket.releaseCategoryName} - ${groupedSeat.ticket.name}`,
|
|
52
60
|
products: [],
|
|
53
61
|
extraFields: [],
|
|
@@ -4,6 +4,7 @@ import { fCurrency } from '@utils/formatNumber';
|
|
|
4
4
|
import { IEvent } from '@utils/types/event.type';
|
|
5
5
|
import { IReleaseShort } from '@utils/types/release.type';
|
|
6
6
|
import { ITicketFormTicket } from '@utils/types/ticket.type';
|
|
7
|
+
import { MAX_TICKETS_PER_ORDER } from '@utils/data/ticket';
|
|
7
8
|
import useGlobal from '@hooks/useGlobal';
|
|
8
9
|
import ReleaseDescription from './ReleaseDescription';
|
|
9
10
|
import TicketQuantityControl from './TicketQuantityControl';
|
|
@@ -12,7 +13,6 @@ interface Props {
|
|
|
12
13
|
event: IEvent;
|
|
13
14
|
activeReleases?: IReleaseShort[];
|
|
14
15
|
showLoading: boolean;
|
|
15
|
-
soldOutReleaseCategoryNames: string[];
|
|
16
16
|
tickets: ITicketFormTicket[];
|
|
17
17
|
isQuantityDisabled: (value: number, releaseId: number | '') => boolean;
|
|
18
18
|
setValue: (name: string, value: any) => void;
|
|
@@ -24,7 +24,6 @@ const TicketSelectionMobile: React.FC<Props> = ({
|
|
|
24
24
|
event,
|
|
25
25
|
activeReleases,
|
|
26
26
|
showLoading,
|
|
27
|
-
soldOutReleaseCategoryNames,
|
|
28
27
|
tickets,
|
|
29
28
|
isQuantityDisabled,
|
|
30
29
|
setValue,
|
|
@@ -47,8 +46,11 @@ const TicketSelectionMobile: React.FC<Props> = ({
|
|
|
47
46
|
return Number(ticket?.quantity || 0);
|
|
48
47
|
};
|
|
49
48
|
|
|
49
|
+
const isReleaseVisible = (release: IReleaseShort) =>
|
|
50
|
+
!release.locked || getReleaseQuantity(release.id) > 0;
|
|
51
|
+
|
|
50
52
|
const updateReleaseQuantity = (release: IReleaseShort, nextQuantity: number) => {
|
|
51
|
-
const maxAvailable = Math.min(release.availableTickets || 0,
|
|
53
|
+
const maxAvailable = Math.min(release.availableTickets || 0, MAX_TICKETS_PER_ORDER);
|
|
52
54
|
const clampedQuantity = Math.max(0, Math.min(nextQuantity, maxAvailable));
|
|
53
55
|
const ticketIndex = getTicketIndexByRelease(release.id);
|
|
54
56
|
|
|
@@ -100,76 +102,84 @@ const TicketSelectionMobile: React.FC<Props> = ({
|
|
|
100
102
|
|
|
101
103
|
return (
|
|
102
104
|
<Stack spacing={2}>
|
|
103
|
-
{activeReleases
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
: fCurrency(release.price, lang, event.currency)}
|
|
105
|
+
{activeReleases
|
|
106
|
+
?.filter((release) => isReleaseVisible(release))
|
|
107
|
+
.map((release) => {
|
|
108
|
+
const quantity = getReleaseQuantity(release.id);
|
|
109
|
+
const ticketIndex = getTicketIndexByRelease(release.id);
|
|
110
|
+
const maxAvailable = Math.min(release.availableTickets || 0, MAX_TICKETS_PER_ORDER);
|
|
111
|
+
const isLocked = release.locked;
|
|
112
|
+
const nextRelease = activeReleases?.find(
|
|
113
|
+
(item) =>
|
|
114
|
+
item.releaseCategoryName === release.releaseCategoryName &&
|
|
115
|
+
item.order === release.order + 1
|
|
116
|
+
);
|
|
117
|
+
const hasSelectedNextRelease = !!nextRelease && getReleaseQuantity(nextRelease.id) > 0;
|
|
118
|
+
const isDisabled = hasSelectedNextRelease || (isLocked && quantity === 0);
|
|
119
|
+
const canAddFirst = maxAvailable > 0 && !isQuantityDisabled(1, release.id);
|
|
120
|
+
const canAddMore =
|
|
121
|
+
quantity < maxAvailable && !isQuantityDisabled(quantity + 1, release.id);
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<Box
|
|
125
|
+
key={release.id}
|
|
126
|
+
sx={{
|
|
127
|
+
pt: 1,
|
|
128
|
+
pr: 0.5,
|
|
129
|
+
pb: 0.5,
|
|
130
|
+
pl: 2,
|
|
131
|
+
borderRadius: 1,
|
|
132
|
+
bgcolor: (theme) => (isLight ? theme.palette.grey[100] : theme.palette.grey[800]),
|
|
133
|
+
}}
|
|
134
|
+
>
|
|
135
|
+
<Stack spacing={0}>
|
|
136
|
+
<Box>
|
|
137
|
+
<Typography variant="subtitle2" fontWeight={700}>
|
|
138
|
+
{getReleaseTitle(release)} - {release.name}
|
|
138
139
|
</Typography>
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
140
|
+
</Box>
|
|
141
|
+
|
|
142
|
+
<Stack direction="row" alignItems="center" justifyContent="space-between">
|
|
143
|
+
<Stack>
|
|
144
|
+
<Typography variant="body2">
|
|
145
|
+
{release.price === 0
|
|
146
|
+
? t('free')
|
|
147
|
+
: fCurrency(release.price, lang, event.currency)}
|
|
148
|
+
</Typography>
|
|
149
|
+
|
|
150
|
+
<ReleaseDescription
|
|
151
|
+
description={release.description}
|
|
152
|
+
isExpanded={Boolean(expandedReleaseIds[release.id])}
|
|
153
|
+
onToggle={() => toggleReleaseDescription(release.id)}
|
|
154
|
+
moreInfoLabel={t('more_info')}
|
|
155
|
+
/>
|
|
156
|
+
</Stack>
|
|
157
|
+
|
|
158
|
+
<TicketQuantityControl
|
|
159
|
+
quantity={quantity}
|
|
160
|
+
isDisabled={isDisabled}
|
|
161
|
+
canAddFirst={canAddFirst}
|
|
162
|
+
canAddMore={canAddMore}
|
|
163
|
+
addLabel={t('add')}
|
|
164
|
+
onDecrement={() => updateReleaseQuantity(release, quantity - 1)}
|
|
165
|
+
onIncrement={() => updateReleaseQuantity(release, quantity + 1)}
|
|
166
|
+
onAddFirst={() => updateReleaseQuantity(release, 1)}
|
|
145
167
|
/>
|
|
146
168
|
</Stack>
|
|
147
169
|
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
onDecrement={() => updateReleaseQuantity(release, quantity - 1)}
|
|
155
|
-
onIncrement={() => updateReleaseQuantity(release, quantity + 1)}
|
|
156
|
-
onAddFirst={() => updateReleaseQuantity(release, 1)}
|
|
170
|
+
<ReleaseDescription
|
|
171
|
+
description={release.description}
|
|
172
|
+
isExpanded={Boolean(expandedReleaseIds[release.id])}
|
|
173
|
+
onToggle={() => toggleReleaseDescription(release.id)}
|
|
174
|
+
moreInfoLabel={t('more_info')}
|
|
175
|
+
showCollapse
|
|
157
176
|
/>
|
|
158
|
-
</Stack>
|
|
159
177
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
showCollapse
|
|
166
|
-
/>
|
|
167
|
-
|
|
168
|
-
{ticketIndex >= 0 && getExtraFields(release.id, ticketIndex)}
|
|
169
|
-
</Stack>
|
|
170
|
-
</Box>
|
|
171
|
-
);
|
|
172
|
-
})}
|
|
178
|
+
{ticketIndex >= 0 && getExtraFields(release.id, ticketIndex)}
|
|
179
|
+
</Stack>
|
|
180
|
+
</Box>
|
|
181
|
+
);
|
|
182
|
+
})}
|
|
173
183
|
</Stack>
|
|
174
184
|
);
|
|
175
185
|
};
|