@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.
@@ -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,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 PlayersAndVariationPicker = ({ 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, flexPlayersOption, }) => {
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(Object.assign(Object.assign({}, _composition), { [key]: Math.max(0, value) }));
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).map((category) => {
469
- var _a, _b;
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 = (_a = maxByCategory[category.key]) !== null && _a !== void 0 ? _a : value;
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: ((_b = category.description) === null || _b === void 0 ? void 0 : _b.trim()) || undefined, value: value, min: 0, max: rowMax, minusLabel: t('prices:minus', { defaultValue: 'На одного меньше' }), plusLabel: t('prices:plus', { defaultValue: 'На одного больше' }), plusDisabledHint: plusLimitHint, onChange: (next) => handleChangeCategory(category.key, next) }));
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.11",
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.10",
18
- "@escapenavigator/types": "^2.2.9",
19
- "@escapenavigator/utils": "^2.2.9",
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": "9ea447d6c73eb90c6369653f557d15dfef025df3"
35
+ "gitHead": "cc2b4544fd91b36fe91063f106e71e48dc0a197e"
36
36
  }