@escapenavigator/hooks 2.2.11 → 2.2.12
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/fortune-wheel/assets.d.ts +2 -0
- package/dist/fortune-wheel/assets.js +6 -0
- package/dist/fortune-wheel/copy.d.ts +6 -0
- package/dist/fortune-wheel/copy.js +50 -0
- package/dist/fortune-wheel/index.d.ts +9 -0
- package/dist/fortune-wheel/index.js +267 -0
- package/dist/fortune-wheel/renderer.d.ts +12 -0
- package/dist/fortune-wheel/renderer.js +1361 -0
- package/dist/players-and-variation-picker/group-composition.d.ts +5 -0
- package/dist/players-and-variation-picker/group-composition.js +30 -0
- package/dist/players-and-variation-picker/group-composition.spec.d.ts +1 -0
- package/dist/players-and-variation-picker/group-composition.spec.js +60 -0
- package/dist/players-and-variation-picker/index.d.ts +11 -9
- package/dist/players-and-variation-picker/index.js +139 -13
- package/dist/use-players-and-variation-picker/types.d.ts +2 -0
- package/dist/use-players-and-variation-picker/types.js +0 -7
- package/package.json +5 -5
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { OrderComposition, TariffCategory } from '@escapenavigator/types/dist/tariff/tariff-category';
|
|
2
|
+
/** Redistribute a fixed group size without changing any other category. */
|
|
3
|
+
export declare const redistributeCategory: (composition: OrderComposition, categories: TariffCategory[], key: string, value: number) => OrderComposition;
|
|
4
|
+
/** Changing the total only changes standard tickets; selected categories survive. */
|
|
5
|
+
export declare const resizeComposition: (composition: OrderComposition, categories: TariffCategory[], total: number) => OrderComposition;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resizeComposition = exports.redistributeCategory = void 0;
|
|
4
|
+
const tariff_category_mode_enum_1 = require("@escapenavigator/types/dist/tariff/enum/tariff-category-mode.enum");
|
|
5
|
+
/** Redistribute a fixed group size without changing any other category. */
|
|
6
|
+
const redistributeCategory = (composition, categories, key, value) => {
|
|
7
|
+
const base = categories.find((category) => category.mode === tariff_category_mode_enum_1.TariffCategoryModeEnum.BASE);
|
|
8
|
+
const category = categories.find((item) => item.key === key);
|
|
9
|
+
if (!base || !category || key === base.key || !Number.isInteger(value) || value < 0)
|
|
10
|
+
return composition;
|
|
11
|
+
const remaining = (composition[base.key] || 0) + (composition[key] || 0) - value;
|
|
12
|
+
if (remaining < 0 ||
|
|
13
|
+
(category.max != null && value > category.max) ||
|
|
14
|
+
(base.max != null && remaining > base.max))
|
|
15
|
+
return composition;
|
|
16
|
+
return Object.assign(Object.assign({}, composition), { [key]: value, [base.key]: remaining });
|
|
17
|
+
};
|
|
18
|
+
exports.redistributeCategory = redistributeCategory;
|
|
19
|
+
/** Changing the total only changes standard tickets; selected categories survive. */
|
|
20
|
+
const resizeComposition = (composition, categories, total) => {
|
|
21
|
+
const base = categories.find((category) => category.mode === tariff_category_mode_enum_1.TariffCategoryModeEnum.BASE);
|
|
22
|
+
if (!base || !Number.isInteger(total))
|
|
23
|
+
return composition;
|
|
24
|
+
const others = categories.reduce((sum, category) => sum + (category.key === base.key ? 0 : composition[category.key] || 0), 0);
|
|
25
|
+
const remaining = total - others;
|
|
26
|
+
if (remaining < 0 || (base.max != null && remaining > base.max))
|
|
27
|
+
return composition;
|
|
28
|
+
return Object.assign(Object.assign({}, composition), { [base.key]: remaining });
|
|
29
|
+
};
|
|
30
|
+
exports.resizeComposition = resizeComposition;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tariff_category_mode_enum_1 = require("@escapenavigator/types/dist/tariff/enum/tariff-category-mode.enum");
|
|
4
|
+
const composition_limits_1 = require("@escapenavigator/utils/dist/composition-limits");
|
|
5
|
+
const composition_price_1 = require("@escapenavigator/utils/dist/composition-price");
|
|
6
|
+
const group_composition_1 = require("./group-composition");
|
|
7
|
+
const categories = [
|
|
8
|
+
{ key: 'adult', title: 'Standard', mode: tariff_category_mode_enum_1.TariffCategoryModeEnum.BASE, value: 0, countsInPrice: true },
|
|
9
|
+
{ key: 'student', title: 'Students', mode: tariff_category_mode_enum_1.TariffCategoryModeEnum.DELTA, value: 100, countsInPrice: true },
|
|
10
|
+
{ key: 'child', title: 'Children', mode: tariff_category_mode_enum_1.TariffCategoryModeEnum.FIXED, value: 200, countsInPrice: false },
|
|
11
|
+
];
|
|
12
|
+
const price = { 2: 2000, 3: 2700, 4: 3200 };
|
|
13
|
+
describe('group composition selection', () => {
|
|
14
|
+
it('replaces standard seats at capacity and preserves the tariff discount', () => {
|
|
15
|
+
const next = (0, group_composition_1.redistributeCategory)({ adult: 4, student: 0, child: 0 }, categories, 'student', 1);
|
|
16
|
+
expect(next).toEqual({ adult: 3, student: 1, child: 0 });
|
|
17
|
+
expect((0, composition_price_1.legacyCountsFromComposition)(next, categories)).toEqual({
|
|
18
|
+
players: 4,
|
|
19
|
+
reducedPlayers: 1,
|
|
20
|
+
children: 0,
|
|
21
|
+
});
|
|
22
|
+
expect((0, composition_limits_1.getCompositionLimits)({ categories, price, composition: next, maxPlayersLimit: 4 })
|
|
23
|
+
.bookable).toBe(true);
|
|
24
|
+
expect((0, composition_price_1.getCompositionPrice)({ categories, price, composition: next })).toBe(3100);
|
|
25
|
+
});
|
|
26
|
+
it('keeps legacy children inside the headcount but charges them on top of the pricing group', () => {
|
|
27
|
+
const next = (0, group_composition_1.redistributeCategory)({ adult: 4, student: 0, child: 0 }, categories, 'child', 1);
|
|
28
|
+
expect(next).toEqual({ adult: 3, student: 0, child: 1 });
|
|
29
|
+
expect((0, composition_price_1.legacyCountsFromComposition)(next, categories)).toEqual({
|
|
30
|
+
players: 3,
|
|
31
|
+
reducedPlayers: 0,
|
|
32
|
+
children: 1,
|
|
33
|
+
});
|
|
34
|
+
expect((0, composition_price_1.getCompositionPrice)({ categories, price, composition: next })).toBe(2900);
|
|
35
|
+
});
|
|
36
|
+
it('returns a category seat to standard and never changes other categories', () => {
|
|
37
|
+
expect((0, group_composition_1.redistributeCategory)({ adult: 1, student: 2, child: 1 }, categories, 'student', 1)).toEqual({ adult: 2, student: 1, child: 1 });
|
|
38
|
+
});
|
|
39
|
+
it('does not allocate more people than selected or exceed category limits', () => {
|
|
40
|
+
const start = { adult: 0, student: 3, child: 1 };
|
|
41
|
+
expect((0, group_composition_1.redistributeCategory)(start, categories, 'child', 2)).toBe(start);
|
|
42
|
+
expect((0, group_composition_1.redistributeCategory)(Object.assign(Object.assign({}, start), { adult: 1 }), categories.map((c) => (Object.assign(Object.assign({}, c), { max: 1 }))), 'student', 4)).toEqual(Object.assign(Object.assign({}, start), { adult: 1 }));
|
|
43
|
+
});
|
|
44
|
+
it('resizes standard seats while retaining selected categories', () => {
|
|
45
|
+
const start = { adult: 2, student: 1, child: 1 };
|
|
46
|
+
expect((0, group_composition_1.resizeComposition)(start, categories, 5)).toEqual({ adult: 3, student: 1, child: 1 });
|
|
47
|
+
expect((0, group_composition_1.resizeComposition)(start, categories, 2)).toEqual({ adult: 0, student: 1, child: 1 });
|
|
48
|
+
expect((0, group_composition_1.resizeComposition)(start, categories, 1)).toBe(start);
|
|
49
|
+
});
|
|
50
|
+
it('keeps genuine pricing restrictions and the minimum price', () => {
|
|
51
|
+
const composition = { adult: 1, student: 0, child: 3 };
|
|
52
|
+
expect((0, composition_limits_1.getCompositionLimits)({ price, categories, composition, maxPlayersLimit: 4 }).bookable).toBe(false);
|
|
53
|
+
expect((0, composition_price_1.getCompositionPrice)({
|
|
54
|
+
price,
|
|
55
|
+
categories,
|
|
56
|
+
composition: { adult: 0, student: 4 },
|
|
57
|
+
minGameAmount: 3000,
|
|
58
|
+
})).toBe(3000);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { OrderTeamDto } from '@escapenavigator/types/dist/order/order-team.dto';
|
|
2
|
+
import { aggregateTeams } from '@escapenavigator/utils/dist/order-teams';
|
|
1
3
|
import { ProfileCurrencyEnum } from '@escapenavigator/types/dist/profile/enum/profile-currency';
|
|
2
4
|
import { SlotDiscountTypeEnum } from '@escapenavigator/types/dist/slot/enum/slot-discount-type.enum';
|
|
3
5
|
import { TariffReducedRateModeEnum } from '@escapenavigator/types/dist/tariff/enum/tariff-reduced-rate-mode.enum';
|
|
@@ -13,6 +15,11 @@ export type LegacyCounts = {
|
|
|
13
15
|
reducedPlayers: number;
|
|
14
16
|
};
|
|
15
17
|
export type PlayersAndVariationPickerProps = {
|
|
18
|
+
teams?: OrderTeamDto[] | null;
|
|
19
|
+
teamSize?: number;
|
|
20
|
+
maxTeams?: number;
|
|
21
|
+
totalSeatsLimit?: number;
|
|
22
|
+
onTeamsChange?: (data: ReturnType<typeof aggregateTeams>) => void;
|
|
16
23
|
/**
|
|
17
24
|
* Minimal slot identity used to fetch enriched details. Pass
|
|
18
25
|
* `null`/`undefined` to disable the variation row entirely.
|
|
@@ -110,6 +117,7 @@ export type PlayersAndVariationPickerProps = {
|
|
|
110
117
|
*/
|
|
111
118
|
onSave?: (data: LegacyCounts & {
|
|
112
119
|
composition: OrderComposition;
|
|
120
|
+
teams?: OrderTeamDto[];
|
|
113
121
|
variation?: PickerVariationChange;
|
|
114
122
|
}) => void;
|
|
115
123
|
/**
|
|
@@ -133,6 +141,8 @@ export type PlayersAndVariationPickerProps = {
|
|
|
133
141
|
* кому строка.
|
|
134
142
|
*/
|
|
135
143
|
hidePriceHints?: boolean;
|
|
144
|
+
/** Widget opts into the tariff UI setting; CRM keeps independent editing. */
|
|
145
|
+
useTariffSelectionMode?: boolean;
|
|
136
146
|
/**
|
|
137
147
|
* Опция «Определимся на месте» (questroom.playersSelectMode=OPTIONAL).
|
|
138
148
|
* Выбрана по умолчанию. Пока flex выбран, ни одно число не подсвечено,
|
|
@@ -150,14 +160,6 @@ export type PlayersAndVariationPickerProps = {
|
|
|
150
160
|
onSelect: (flex: boolean) => void;
|
|
151
161
|
};
|
|
152
162
|
};
|
|
153
|
-
/**
|
|
154
|
-
* Единый пикер «вариация + состав», который используют админские модалки
|
|
155
|
-
* заказа, виджет и пакет orders.
|
|
156
|
-
*
|
|
157
|
-
* Состав описывается категориями участников тарифа: взрослые, дети до 7,
|
|
158
|
-
* студенты, пенсионеры — все одинаковыми строками со степпером. Цена
|
|
159
|
-
* категории считается от доли групповой цены на текущий состав, поэтому она
|
|
160
|
-
* живая: гость в команде из шести дешевле, чем в команде из двух.
|
|
161
|
-
*/
|
|
163
|
+
/** A single booking: only the composition differs between teams. */
|
|
162
164
|
export declare const PlayersAndVariationPicker: React.FC<PlayersAndVariationPickerProps>;
|
|
163
165
|
export {};
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/* eslint-disable complexity */
|
|
3
|
-
/* eslint-disable no-underscore-dangle */
|
|
4
|
-
/* eslint-disable @typescript-eslint/naming-convention */
|
|
5
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
3
|
if (k2 === undefined) k2 = k;
|
|
7
4
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -40,6 +37,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
40
37
|
};
|
|
41
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
39
|
exports.PlayersAndVariationPicker = void 0;
|
|
40
|
+
const order_teams_1 = require("@escapenavigator/utils/dist/order-teams");
|
|
41
|
+
/* eslint-disable complexity */
|
|
42
|
+
/* eslint-disable no-underscore-dangle */
|
|
43
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
43
44
|
const InfoMarkS_1 = __importDefault(require("@alphakits/icons/dist/InfoMarkS"));
|
|
44
45
|
const button_1 = require("@alphakits/ui/dist/button");
|
|
45
46
|
const flex_1 = require("@alphakits/ui/dist/flex");
|
|
@@ -49,6 +50,7 @@ const tag_1 = require("@alphakits/ui/dist/tag");
|
|
|
49
50
|
const toast_plate_1 = require("@alphakits/ui/dist/toast-plate");
|
|
50
51
|
const typography_1 = require("@alphakits/ui/dist/typography");
|
|
51
52
|
const tariff_category_mode_enum_1 = require("@escapenavigator/types/dist/tariff/enum/tariff-category-mode.enum");
|
|
53
|
+
const tariff_selection_mode_enum_1 = require("@escapenavigator/types/dist/tariff/enum/tariff-selection-mode.enum");
|
|
52
54
|
const composition_limits_1 = require("@escapenavigator/utils/dist/composition-limits");
|
|
53
55
|
const composition_price_1 = require("@escapenavigator/utils/dist/composition-price");
|
|
54
56
|
const format_amount_1 = require("@escapenavigator/utils/dist/format-amount");
|
|
@@ -57,6 +59,7 @@ const reduced_price_1 = require("@escapenavigator/utils/dist/reduced-price");
|
|
|
57
59
|
const react_1 = __importStar(require("react"));
|
|
58
60
|
const react_i18next_1 = require("react-i18next");
|
|
59
61
|
const use_players_and_variation_picker_1 = require("../use-players-and-variation-picker");
|
|
62
|
+
const group_composition_1 = require("./group-composition");
|
|
60
63
|
const sync_composition_1 = require("./sync-composition");
|
|
61
64
|
const ticket_row_1 = require("./ticket-row");
|
|
62
65
|
/**
|
|
@@ -91,7 +94,7 @@ const playersAlreadyDecidedLabel = (translate, language) => {
|
|
|
91
94
|
* категории считается от доли групповой цены на текущий состав, поэтому она
|
|
92
95
|
* живая: гость в команде из шести дешевле, чем в команде из двух.
|
|
93
96
|
*/
|
|
94
|
-
const
|
|
97
|
+
const SinglePlayersPicker = ({ sharedVariation, slot, currentTariffId, currentDuration, loadSlotDetails, onVariationChange, onVariationResolved, onDetailsLoaded, composition: compositionProp, categories: categoriesOverride, onCompositionChange, players = 0, child = 0, reducedPlayers = 0, reduced: reducedOverride, onReducedChange, tariff: tariffOverride, currency, minPlayers, maxPlayersLimit, slotDiscount, slotDiscountType, error, t, onChange, onSave, orderId, onLoadingChange, onValidityChange, hidePriceHints = false, useTariffSelectionMode = false, flexPlayersOption, }) => {
|
|
95
98
|
var _a;
|
|
96
99
|
/**
|
|
97
100
|
* Страховка от «голых ключей» перевода: хост часто берёт `t` из
|
|
@@ -101,7 +104,7 @@ const PlayersAndVariationPicker = ({ slot, currentTariffId, currentDuration, loa
|
|
|
101
104
|
const { ready: translationsReady, i18n } = (0, react_i18next_1.useTranslation)(['prices', 'common'], {
|
|
102
105
|
useSuspense: false,
|
|
103
106
|
});
|
|
104
|
-
const { loading: pickerLoading, options: pickerOptions, selectedOptionId, selectedVariation, buildVariationChange, selectVariation, details, } = (0, use_players_and_variation_picker_1.usePlayersAndVariationPicker)({
|
|
107
|
+
const { loading: pickerLoading, options: pickerOptions, selectedOptionId, selectedVariation: loadedVariation, buildVariationChange, selectVariation, details, } = (0, use_players_and_variation_picker_1.usePlayersAndVariationPicker)({
|
|
105
108
|
slot,
|
|
106
109
|
currentTariffId,
|
|
107
110
|
currentDuration,
|
|
@@ -109,6 +112,7 @@ const PlayersAndVariationPicker = ({ slot, currentTariffId, currentDuration, loa
|
|
|
109
112
|
t: t,
|
|
110
113
|
orderId,
|
|
111
114
|
});
|
|
115
|
+
const selectedVariation = sharedVariation || loadedVariation;
|
|
112
116
|
const onLoadingChangeRef = (0, react_1.useRef)(onLoadingChange);
|
|
113
117
|
onLoadingChangeRef.current = onLoadingChange;
|
|
114
118
|
const pickedVariationRef = (0, react_1.useRef)(null);
|
|
@@ -235,6 +239,11 @@ const PlayersAndVariationPicker = ({ slot, currentTariffId, currentDuration, loa
|
|
|
235
239
|
const { capacityMax, pricingMax, effectiveMin, pricingHeads, totalHeads, levelUnavailable, belowMin, capacityReached, maxByCategory, baseLevelOptions, } = limits;
|
|
236
240
|
const baseCategory = categories.find((category) => category.mode === tariff_category_mode_enum_1.TariffCategoryModeEnum.BASE);
|
|
237
241
|
const baseCategoryKey = baseCategory === null || baseCategory === void 0 ? void 0 : baseCategory.key;
|
|
242
|
+
const groupMode = useTariffSelectionMode &&
|
|
243
|
+
(0, composition_price_1.hasExtraCategories)(categories) &&
|
|
244
|
+
(selectedVariation === null || selectedVariation === void 0 ? void 0 : selectedVariation.selectionMode) === tariff_selection_mode_enum_1.TariffSelectionModeEnum.COMPOSITION;
|
|
245
|
+
const standardCount = baseCategoryKey ? _composition[baseCategoryKey] || 0 : 0;
|
|
246
|
+
const categoryHeads = totalHeads - standardCount;
|
|
238
247
|
const applyComposition = (next) => {
|
|
239
248
|
pendingComposition.current = next;
|
|
240
249
|
setComposition(next);
|
|
@@ -248,7 +257,9 @@ const PlayersAndVariationPicker = ({ slot, currentTariffId, currentDuration, loa
|
|
|
248
257
|
onReducedChange === null || onReducedChange === void 0 ? void 0 : onReducedChange(legacy.reducedPlayers);
|
|
249
258
|
};
|
|
250
259
|
const handleChangeCategory = (key, value) => {
|
|
251
|
-
applyComposition(
|
|
260
|
+
applyComposition(groupMode
|
|
261
|
+
? (0, group_composition_1.redistributeCategory)(_composition, categories, key, value)
|
|
262
|
+
: Object.assign(Object.assign({}, _composition), { [key]: Math.max(0, value) }));
|
|
252
263
|
// Явный выбор состава снимает «Определимся на месте».
|
|
253
264
|
if (flexSelected)
|
|
254
265
|
flexPlayersOption === null || flexPlayersOption === void 0 ? void 0 : flexPlayersOption.onSelect(false);
|
|
@@ -282,11 +293,13 @@ const PlayersAndVariationPicker = ({ slot, currentTariffId, currentDuration, loa
|
|
|
282
293
|
/**
|
|
283
294
|
* Единственная автоправка состава: смена вариации сузила сетку, и состав
|
|
284
295
|
* торчит выше нового потолка — иначе на бэке `price[N]` = undefined →
|
|
285
|
-
* NaN → 500.
|
|
296
|
+
* NaN → 500. В режиме фиксированного состава сохраняем число гостей:
|
|
297
|
+
* недоступную цену блокирует bookable, гость сам уточняет состав.
|
|
298
|
+
* Внутри сетки состав не трогаем даже на закрытом уровне.
|
|
286
299
|
*/
|
|
287
300
|
// biome-ignore lint/correctness/useExhaustiveDependencies: односторонний clamp
|
|
288
301
|
(0, react_1.useEffect)(() => {
|
|
289
|
-
if (flexSelected)
|
|
302
|
+
if (flexSelected || groupMode)
|
|
290
303
|
return;
|
|
291
304
|
const clamped = (0, composition_limits_1.clampCompositionToGrid)({
|
|
292
305
|
price: tariff,
|
|
@@ -298,7 +311,7 @@ const PlayersAndVariationPicker = ({ slot, currentTariffId, currentDuration, loa
|
|
|
298
311
|
if (clamped)
|
|
299
312
|
applyComposition(clamped);
|
|
300
313
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
301
|
-
}, [pricingMax, pricingHeads, flexSelected]);
|
|
314
|
+
}, [pricingMax, pricingHeads, flexSelected, groupMode]);
|
|
302
315
|
const perPerson = (0, composition_price_1.getPerPersonAmount)(tariff, pricingHeads);
|
|
303
316
|
const bookable = flexSelected || limits.bookable;
|
|
304
317
|
const onValidityChangeRef = (0, react_1.useRef)(onValidityChange);
|
|
@@ -464,11 +477,35 @@ const PlayersAndVariationPicker = ({ slot, currentTariffId, currentDuration, loa
|
|
|
464
477
|
baseLevelOptions.map((level) => (react_1.default.createElement(tag_1.Tag, { key: `level-${level}`, text: `${level}`, onClick: () => handleSelectLevel(level), view: !flexSelected && pricingHeads === level
|
|
465
478
|
? 'primary-inverted'
|
|
466
479
|
: 'primary' })))))),
|
|
480
|
+
groupMode && !flexSelected && (react_1.default.createElement(flex_columns_1.FlexColumns, { columns: 1, gr: 8 },
|
|
481
|
+
react_1.default.createElement(ticket_row_1.TicketRow, { title: t('prices:composition.total', {
|
|
482
|
+
defaultValue: 'Total participants',
|
|
483
|
+
}), description: t('prices:composition.totalHelp', {
|
|
484
|
+
defaultValue: 'Including children and guests with concessions. To lower the total below the selected categories, reduce their counts first.',
|
|
485
|
+
}), priceHint: '', value: totalHeads, min: Math.max(effectiveMin, categoryHeads), max: Math.min(capacityMax, (baseCategory === null || baseCategory === void 0 ? void 0 : baseCategory.max) == null
|
|
486
|
+
? capacityMax
|
|
487
|
+
: categoryHeads + baseCategory.max), minusLabel: t('prices:minus', {
|
|
488
|
+
defaultValue: 'One fewer participant',
|
|
489
|
+
}), plusLabel: t('prices:plus', { defaultValue: 'One more participant' }), onChange: (next) => applyComposition((0, group_composition_1.resizeComposition)(_composition, categories, next)) }),
|
|
490
|
+
react_1.default.createElement(typography_1.Typography.Text, { view: 'caps', color: 'secondary' }, t('prices:composition.help', {
|
|
491
|
+
defaultValue: 'Specify who is in your group. These categories replace standard tickets; the total number of people stays the same.',
|
|
492
|
+
})),
|
|
493
|
+
react_1.default.createElement(typography_1.Typography.Text, { view: 'primary-small', weight: 'medium' }, t('prices:composition.standard', {
|
|
494
|
+
count: standardCount,
|
|
495
|
+
defaultValue: 'Standard tickets: {{count}}',
|
|
496
|
+
})))),
|
|
467
497
|
(hasExtraRows ? !flexSelected : true) &&
|
|
468
|
-
(hasExtraRows ? categories : addOnCategories)
|
|
469
|
-
|
|
498
|
+
(hasExtraRows ? categories : addOnCategories)
|
|
499
|
+
.filter((category) => !groupMode || category.key !== baseCategoryKey)
|
|
500
|
+
.map((category) => {
|
|
501
|
+
var _a, _b, _c;
|
|
470
502
|
const value = Math.max(0, _composition[category.key] || 0);
|
|
471
|
-
const rowMax =
|
|
503
|
+
const rowMax = groupMode
|
|
504
|
+
? Math.min(value + standardCount, (_a = category.max) !== null && _a !== void 0 ? _a : Number.POSITIVE_INFINITY)
|
|
505
|
+
: ((_b = maxByCategory[category.key]) !== null && _b !== void 0 ? _b : value);
|
|
506
|
+
const rowMin = groupMode && (baseCategory === null || baseCategory === void 0 ? void 0 : baseCategory.max) != null
|
|
507
|
+
? Math.max(0, value + standardCount - baseCategory.max)
|
|
508
|
+
: 0;
|
|
472
509
|
const plusLimitHint = value >= rowMax
|
|
473
510
|
? capacityReached
|
|
474
511
|
? t('prices:capacityReached', {
|
|
@@ -480,7 +517,15 @@ const PlayersAndVariationPicker = ({ slot, currentTariffId, currentDuration, loa
|
|
|
480
517
|
defaultValue: 'Максимум {{count}} участников по этому тарифу',
|
|
481
518
|
})
|
|
482
519
|
: undefined;
|
|
483
|
-
return (react_1.default.createElement(ticket_row_1.TicketRow, { key: category.key, title: categoryTitle(category), priceHint: categoryPriceHint(category), description: ((
|
|
520
|
+
return (react_1.default.createElement(ticket_row_1.TicketRow, { key: category.key, title: categoryTitle(category), priceHint: categoryPriceHint(category), description: ((_c = category.description) === null || _c === void 0 ? void 0 : _c.trim()) || undefined, value: value, min: rowMin, max: rowMax, minusLabel: t('prices:minus', {
|
|
521
|
+
defaultValue: 'На одного меньше',
|
|
522
|
+
}), plusLabel: t('prices:plus', {
|
|
523
|
+
defaultValue: 'На одного больше',
|
|
524
|
+
}), plusDisabledHint: groupMode
|
|
525
|
+
? t('prices:composition.limit', {
|
|
526
|
+
defaultValue: 'All standard tickets have been allocated or the category limit has been reached. Adjust the categories or the total number of participants.',
|
|
527
|
+
})
|
|
528
|
+
: plusLimitHint, onChange: (next) => handleChangeCategory(category.key, next) }));
|
|
484
529
|
})),
|
|
485
530
|
!hidePriceHints && !flexSelected && bookable && (react_1.default.createElement(flex_columns_1.FlexColumns, { columns: 1, gr: 4, gc: 0 }, priceSummary)),
|
|
486
531
|
hidePriceHints && !flexSelected && bookable && (react_1.default.createElement("div", { style: {
|
|
@@ -505,4 +550,85 @@ const PlayersAndVariationPicker = ({ slot, currentTariffId, currentDuration, loa
|
|
|
505
550
|
? { variation: pickedVariationRef.current }
|
|
506
551
|
: {}))), view: 'primary', size: 'xs', disabled: !bookable }, t('common:save')))));
|
|
507
552
|
};
|
|
553
|
+
/** A single booking: only the composition differs between teams. */
|
|
554
|
+
const PlayersAndVariationPicker = (props) => {
|
|
555
|
+
var _a, _b;
|
|
556
|
+
const { t } = (0, react_i18next_1.useTranslation)('widgets');
|
|
557
|
+
const [draft, setDraft] = (0, react_1.useState)(null);
|
|
558
|
+
const [variation, setVariation] = (0, react_1.useState)();
|
|
559
|
+
const [variationChange, setVariationChange] = (0, react_1.useState)();
|
|
560
|
+
const [valid, setValid] = (0, react_1.useState)({});
|
|
561
|
+
const teams = draft || props.teams;
|
|
562
|
+
const nextTeamKey = (0, react_1.useRef)(0);
|
|
563
|
+
const teamKeys = (0, react_1.useRef)([]);
|
|
564
|
+
while (teamKeys.current.length < ((teams === null || teams === void 0 ? void 0 : teams.length) || 0))
|
|
565
|
+
teamKeys.current.push(nextTeamKey.current++);
|
|
566
|
+
const teamsRef = (0, react_1.useRef)(teams);
|
|
567
|
+
teamsRef.current = teams;
|
|
568
|
+
const seats = (teams === null || teams === void 0 ? void 0 : teams.reduce((n, team) => n + team.players + (team.children || 0), 0)) || 0;
|
|
569
|
+
const maxTeams = (_a = props.maxTeams) !== null && _a !== void 0 ? _a : 100;
|
|
570
|
+
const availableSeats = (_b = props.totalSeatsLimit) !== null && _b !== void 0 ? _b : Infinity;
|
|
571
|
+
const bookable = !!(teams === null || teams === void 0 ? void 0 : teams.length) &&
|
|
572
|
+
teams.length <= maxTeams &&
|
|
573
|
+
seats <= availableSeats &&
|
|
574
|
+
teams.every((_, index) => valid[teamKeys.current[index]] === true);
|
|
575
|
+
const notify = (0, react_1.useRef)(props.onValidityChange);
|
|
576
|
+
notify.current = props.onValidityChange;
|
|
577
|
+
(0, react_1.useEffect)(() => {
|
|
578
|
+
var _a;
|
|
579
|
+
if (teams === null || teams === void 0 ? void 0 : teams.length)
|
|
580
|
+
(_a = notify.current) === null || _a === void 0 ? void 0 : _a.call(notify, bookable);
|
|
581
|
+
}, [bookable, teams === null || teams === void 0 ? void 0 : teams.length]);
|
|
582
|
+
if (!(teams === null || teams === void 0 ? void 0 : teams.length))
|
|
583
|
+
return react_1.default.createElement(SinglePlayersPicker, Object.assign({}, props));
|
|
584
|
+
const change = (next) => {
|
|
585
|
+
var _a;
|
|
586
|
+
teamsRef.current = next;
|
|
587
|
+
setDraft(next);
|
|
588
|
+
(_a = props.onTeamsChange) === null || _a === void 0 ? void 0 : _a.call(props, (0, order_teams_1.aggregateTeams)(next));
|
|
589
|
+
};
|
|
590
|
+
const min = (variation === null || variation === void 0 ? void 0 : variation.minPlayers) || props.minPlayers || 1;
|
|
591
|
+
return (react_1.default.createElement(flex_columns_1.FlexColumns, { columns: 1, gr: 16 },
|
|
592
|
+
teams.map((team, index) => {
|
|
593
|
+
var _a, _b;
|
|
594
|
+
return (react_1.default.createElement(flex_columns_1.FlexColumns, { key: teamKeys.current[index], columns: 1, gr: 8 },
|
|
595
|
+
react_1.default.createElement(flex_1.Flex, { justify: 'between', align: 'center' },
|
|
596
|
+
react_1.default.createElement(typography_1.Typography.Text, { weight: 'bold' }, t('bookingTeams.team', {
|
|
597
|
+
defaultValue: 'Team {{number}}',
|
|
598
|
+
number: index + 1,
|
|
599
|
+
})),
|
|
600
|
+
teams.length > 1 && (react_1.default.createElement(button_1.Button, { size: 's', onClick: () => {
|
|
601
|
+
teamKeys.current.splice(index, 1);
|
|
602
|
+
change(teams.filter((_, i) => i !== index));
|
|
603
|
+
} }, t('bookingTeams.remove', {
|
|
604
|
+
defaultValue: 'Remove team {{number}}',
|
|
605
|
+
number: index + 1,
|
|
606
|
+
})))),
|
|
607
|
+
react_1.default.createElement(SinglePlayersPicker, Object.assign({}, props, { teams: undefined, slot: index === 0 ? props.slot : null, onSave: undefined, onChange: undefined, onReducedChange: undefined, flexPlayersOption: undefined, players: team.players, child: team.children, reducedPlayers: team.reducedPlayers, composition: team.composition, categories: props.categories || (variation === null || variation === void 0 ? void 0 : variation.categories), tariff: props.tariff || (variation === null || variation === void 0 ? void 0 : variation.tariff), reduced: props.reduced || variation, sharedVariation: index === 0 ? undefined : variation, slotDiscount: undefined, slotDiscountType: undefined, maxPlayersLimit: Math.max(0, Math.min((_b = (_a = props.teamSize) !== null && _a !== void 0 ? _a : props.maxPlayersLimit) !== null && _b !== void 0 ? _b : Infinity, availableSeats - seats + team.players + (team.children || 0))), onCompositionChange: (composition, legacy) => change(teamsRef.current.map((item, i) => i === index ? Object.assign(Object.assign({}, legacy), { composition }) : item)), onVariationResolved: index === 0
|
|
608
|
+
? (next) => {
|
|
609
|
+
var _a;
|
|
610
|
+
setVariation(next);
|
|
611
|
+
(_a = props.onVariationResolved) === null || _a === void 0 ? void 0 : _a.call(props, next);
|
|
612
|
+
}
|
|
613
|
+
: undefined, onVariationChange: index === 0
|
|
614
|
+
? (next) => {
|
|
615
|
+
var _a;
|
|
616
|
+
setVariationChange(next);
|
|
617
|
+
(_a = props.onVariationChange) === null || _a === void 0 ? void 0 : _a.call(props, next);
|
|
618
|
+
}
|
|
619
|
+
: undefined, onLoadingChange: index === 0 ? props.onLoadingChange : undefined, onDetailsLoaded: index === 0 ? props.onDetailsLoaded : undefined, onValidityChange: (value) => setValid((old) => old[teamKeys.current[index]] === value
|
|
620
|
+
? old
|
|
621
|
+
: Object.assign(Object.assign({}, old), { [teamKeys.current[index]]: value })), hidePriceHints: false }))));
|
|
622
|
+
}),
|
|
623
|
+
react_1.default.createElement(button_1.Button, { block: true, size: 's', disabled: teams.length >= maxTeams || seats + min > availableSeats, onClick: () => change([...teams, { players: min, children: 0, reducedPlayers: 0 }]) }, t('bookingTeams.add', { defaultValue: 'Add another team' })),
|
|
624
|
+
react_1.default.createElement(typography_1.Typography.Text, { color: 'secondary' }, t('bookingTeams.settings', {
|
|
625
|
+
defaultValue: 'Each team plays separately. The game settings below apply to every team.',
|
|
626
|
+
})),
|
|
627
|
+
react_1.default.createElement(typography_1.Typography.Text, { weight: 'bold' }, t('bookingTeams.summary', {
|
|
628
|
+
defaultValue: 'Teams: {{teams}} · Players: {{players}}',
|
|
629
|
+
teams: teams.length,
|
|
630
|
+
players: seats,
|
|
631
|
+
})),
|
|
632
|
+
props.onSave && (react_1.default.createElement(button_1.Button, { block: true, disabled: !bookable, onClick: () => { var _a; return (_a = props.onSave) === null || _a === void 0 ? void 0 : _a.call(props, Object.assign(Object.assign({}, (0, order_teams_1.aggregateTeams)(teams)), { variation: variationChange })); } }, props.t('common:save')))));
|
|
633
|
+
};
|
|
508
634
|
exports.PlayersAndVariationPicker = PlayersAndVariationPicker;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TariffSelectionModeEnum } from '@escapenavigator/types/dist/tariff/enum/tariff-selection-mode.enum';
|
|
1
2
|
/**
|
|
2
3
|
* Shared shapes used by `usePlayersAndVariationPicker`.
|
|
3
4
|
*
|
|
@@ -30,6 +31,7 @@ export type PickerSlotVariation = {
|
|
|
30
31
|
* их собирает `categoriesFromLegacy`, поэтому клиенту про старый формат
|
|
31
32
|
* знать не нужно.
|
|
32
33
|
*/
|
|
34
|
+
selectionMode?: TariffSelectionModeEnum;
|
|
33
35
|
categories?: TariffCategory[];
|
|
34
36
|
childPriceDescription?: string | null;
|
|
35
37
|
/**
|
|
@@ -1,9 +1,2 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Shared shapes used by `usePlayersAndVariationPicker`.
|
|
4
|
-
*
|
|
5
|
-
* They intentionally mirror both admin (`SlotRO`/`SlotEnrichedRO`) and widget
|
|
6
|
-
* (`OpenapiSlotRO`/`OpenapiSlotEnrichedRO`) contracts so a single picker can
|
|
7
|
-
* be plugged into either context.
|
|
8
|
-
*/
|
|
9
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@escapenavigator/hooks",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.12",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
"test": "jest"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@escapenavigator/services": "^2.2.
|
|
18
|
-
"@escapenavigator/types": "^2.2.
|
|
19
|
-
"@escapenavigator/utils": "^2.2.
|
|
17
|
+
"@escapenavigator/services": "^2.2.11",
|
|
18
|
+
"@escapenavigator/types": "^2.2.10",
|
|
19
|
+
"@escapenavigator/utils": "^2.2.10",
|
|
20
20
|
"libphonenumber-js": "^1.12.24",
|
|
21
21
|
"react": "19.2.6",
|
|
22
22
|
"react-phone-input-2": "^2.15.1"
|
|
@@ -32,5 +32,5 @@
|
|
|
32
32
|
"ts-jest": "^29.1.1",
|
|
33
33
|
"typescript": "^5.6"
|
|
34
34
|
},
|
|
35
|
-
"gitHead": "
|
|
35
|
+
"gitHead": "cc2b4544fd91b36fe91063f106e71e48dc0a197e"
|
|
36
36
|
}
|