@escapenavigator/hooks 2.0.47 → 2.0.49
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.
|
@@ -56,6 +56,7 @@ const reduced_price_1 = require("@escapenavigator/utils/dist/reduced-price");
|
|
|
56
56
|
const react_1 = __importStar(require("react"));
|
|
57
57
|
const react_i18next_1 = require("react-i18next");
|
|
58
58
|
const use_players_and_variation_picker_1 = require("../use-players-and-variation-picker");
|
|
59
|
+
const ticket_row_1 = require("./ticket-row");
|
|
59
60
|
/**
|
|
60
61
|
* Single shared component that renders the "Variation + Players" picker used
|
|
61
62
|
* by the admin order modals, the widget order flow and the orders package.
|
|
@@ -240,26 +241,23 @@ const PlayersAndVariationPicker = ({ slot, currentTariffId, currentDuration, loa
|
|
|
240
241
|
const reducedTitle = (_b = (_a = reducedOverride === null || reducedOverride === void 0 ? void 0 : reducedOverride.reducedTitle) !== null && _a !== void 0 ? _a : selectedVariation === null || selectedVariation === void 0 ? void 0 : selectedVariation.reducedTitle) !== null && _b !== void 0 ? _b : '';
|
|
241
242
|
const reducedDescription = ((_d = (_c = reducedOverride === null || reducedOverride === void 0 ? void 0 : reducedOverride.reducedPriceDescription) !== null && _c !== void 0 ? _c : selectedVariation === null || selectedVariation === void 0 ? void 0 : selectedVariation.reducedPriceDescription) !== null && _d !== void 0 ? _d : '').trim();
|
|
242
243
|
const hasReducedRate = !!reducedConfig.reducedRateMode && !!reducedConfig.reducedRate;
|
|
243
|
-
//
|
|
244
|
-
//
|
|
245
|
-
const maxReduced =
|
|
246
|
-
? (0, reduced_price_1.getMaxReducedPlayers)(Object.assign({ price: tariff, players: _players }, reducedConfig))
|
|
247
|
-
: 0, [hasReducedRate, tariff, _players, reducedConfig]);
|
|
248
|
-
const reducedCounts = (0, react_1.useMemo)(() => Array.from({ length: maxReduced + 1 }, (_, index) => index), [maxReduced]);
|
|
244
|
+
// Льготных мест не больше, чем людей в составе. Пол выручки режет сумму,
|
|
245
|
+
// а не выбор: три школьника на четверых можно отметить всегда.
|
|
246
|
+
const maxReduced = hasReducedRate ? _players : 0;
|
|
249
247
|
const handleChangeReduced = (value) => {
|
|
250
248
|
setReduced(value);
|
|
251
249
|
onReducedChange === null || onReducedChange === void 0 ? void 0 : onReducedChange(value);
|
|
252
250
|
};
|
|
253
|
-
// Состав уменьшился
|
|
254
|
-
//
|
|
255
|
-
// `reducedPlayersLimitExceeded` уже на сохранении.
|
|
251
|
+
// Состав уменьшился — вчерашние 3 льготных места могут стать больше
|
|
252
|
+
// группы. Подтягиваем к числу гостей, иначе бэк отдаст ошибку.
|
|
256
253
|
// biome-ignore lint/correctness/useExhaustiveDependencies: односторонний clamp
|
|
257
254
|
(0, react_1.useEffect)(() => {
|
|
258
255
|
if (_reduced > maxReduced)
|
|
259
256
|
handleChangeReduced(maxReduced);
|
|
260
257
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
261
258
|
}, [maxReduced, _reduced]);
|
|
262
|
-
const
|
|
259
|
+
const appliedReduced = Math.min(_reduced, maxReduced);
|
|
260
|
+
const teamPriceBase = (0, get_players_price_1.getPlayersPrice)(Object.assign({ players: _players, children: _children, price: tariff, reducedPlayers: appliedReduced }, reducedConfig));
|
|
263
261
|
const teamPrice = teamPriceBase -
|
|
264
262
|
(0, get_slot_discount_amount_1.getSlotDiscountAmount)({
|
|
265
263
|
price: teamPriceBase,
|
|
@@ -269,15 +267,18 @@ const PlayersAndVariationPicker = ({ slot, currentTariffId, currentDuration, loa
|
|
|
269
267
|
});
|
|
270
268
|
const totalHeads = _players + (_children || 0);
|
|
271
269
|
const personPrice = totalHeads > 0 ? Math.round(teamPrice / totalHeads / 100) * 100 : 0;
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
270
|
+
const seatDiscount = hasReducedRate
|
|
271
|
+
? (0, reduced_price_1.getReducedSeatDiscount)(Object.assign({ price: tariff, players: _players }, reducedConfig))
|
|
272
|
+
: 0;
|
|
273
|
+
const priceWithoutReduced = (0, get_players_price_1.getPlayersPrice)(Object.assign({ players: _players, children: _children, price: tariff, reducedPlayers: 0 }, reducedConfig));
|
|
274
|
+
const floorReached = appliedReduced > 0 && appliedReduced * seatDiscount > priceWithoutReduced - teamPriceBase;
|
|
275
|
+
const minAmountLabel = (0, format_amount_1.formatAmount)((0, reduced_price_1.getMinGameAmount)(reducedConfig), currency);
|
|
276
|
+
const maxChildren = Math.max(0, effectiveMax - _players);
|
|
276
277
|
const showVariations = pickerOptions.length > 1;
|
|
277
278
|
// Клиентское описание показываем только для вариаций — у стандартного
|
|
278
279
|
// (default) тарифа описание не выводим.
|
|
279
280
|
const variationDescription = selectedVariation && !selectedVariation.isDefault
|
|
280
|
-
? ((
|
|
281
|
+
? ((_e = selectedVariation.description) !== null && _e !== void 0 ? _e : null)
|
|
281
282
|
: null;
|
|
282
283
|
const handleVariationClick = (optionId, isFree) => {
|
|
283
284
|
if (!isFree)
|
|
@@ -318,7 +319,10 @@ const PlayersAndVariationPicker = ({ slot, currentTariffId, currentDuration, loa
|
|
|
318
319
|
react_1.default.createElement(flex_columns_1.FlexColumns, { columns: 1, gr: 8 },
|
|
319
320
|
react_1.default.createElement(flex_1.Flex, { gap: 'sm', align: 'center', justify: 'between' },
|
|
320
321
|
react_1.default.createElement(typography_1.Typography.Text, { view: 'title', color: 'secondary' }, tariff.child ? t('prices:adults') : t('prices:choosePlayersAmount')),
|
|
321
|
-
!flexSelected && !!
|
|
322
|
+
!flexSelected && !!_players && tariff[_players] != null && (react_1.default.createElement(typography_1.Typography.Text, { view: 'title', color: 'secondary' }, (0, format_amount_1.formatAmount)(tariff[_players], currency)))),
|
|
323
|
+
(hasReducedRate || !!childrenOptions.length) && (react_1.default.createElement(typography_1.Typography.Text, { view: 'caps', color: 'secondary' }, t('prices:groupFromTariff', {
|
|
324
|
+
defaultValue: 'Цена группы по тарифу на этот состав',
|
|
325
|
+
}))),
|
|
322
326
|
react_1.default.createElement(flex_1.Flex, { gap: 'xs', wrap: true, justify: 'start' },
|
|
323
327
|
!!flexPlayersOption && (react_1.default.createElement(tag_1.Tag, { key: 'flex-players', text: flexPlayersOption.label, onClick: handleSelectFlex, view: flexSelected ? 'primary-inverted' : 'primary' })),
|
|
324
328
|
playersOptions
|
|
@@ -329,47 +333,38 @@ const PlayersAndVariationPicker = ({ slot, currentTariffId, currentDuration, loa
|
|
|
329
333
|
.map((p) => (react_1.default.createElement(tag_1.Tag, { key: `adult-${p.key}`, text: `${p.key}`, onClick: () => handleChangePlayers(p.key), view: !flexSelected && _players === p.key
|
|
330
334
|
? 'primary-inverted'
|
|
331
335
|
: 'primary' }))))),
|
|
332
|
-
!flexSelected && !!
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
!hidePriceHints && !flexSelected && !!totalHeads && (react_1.default.createElement(flex_1.Flex, { justify: 'start', gap: 'xs' },
|
|
365
|
-
react_1.default.createElement(InfoMarkS_1.default, { style: { fill: 'var(--color-text-secondary)' } }),
|
|
366
|
-
react_1.default.createElement(typography_1.Typography.Text, { view: 'caps', color: 'secondary' }, t('prices:totalPriceDescription', {
|
|
367
|
-
teamPrice: (0, format_amount_1.formatAmount)(teamPrice, currency),
|
|
368
|
-
personPrice: (0, format_amount_1.formatAmount)(personPrice, currency),
|
|
369
|
-
})))),
|
|
370
|
-
hidePriceHints &&
|
|
371
|
-
!flexSelected &&
|
|
372
|
-
(!!totalHeads || !!childDescription || (!!_reduced && !!reducedDescription)) && (react_1.default.createElement("div", { style: {
|
|
336
|
+
!flexSelected && hasReducedRate && !!_players && (react_1.default.createElement(ticket_row_1.TicketRow, { title: reducedTitle ||
|
|
337
|
+
t('prices:reducedPlayers', { defaultValue: 'Льготные места' }), priceHint: seatDiscount
|
|
338
|
+
? t('prices:reducedFromGroup', {
|
|
339
|
+
amount: (0, format_amount_1.formatAmount)(seatDiscount, currency),
|
|
340
|
+
defaultValue: '− {{amount}} от цены группы по тарифу, с человека',
|
|
341
|
+
})
|
|
342
|
+
: t('prices:reducedAlreadyInTariff', {
|
|
343
|
+
defaultValue: 'Уже заложено в цену группы на этот состав',
|
|
344
|
+
}), description: !hidePriceHints ? reducedDescription || undefined : undefined, value: appliedReduced, min: 0, max: maxReduced, minusLabel: t('prices:minus', { defaultValue: 'На одного меньше' }), plusLabel: t('prices:plus', { defaultValue: 'На одного больше' }), onChange: handleChangeReduced })),
|
|
345
|
+
!flexSelected && !!childrenOptions.length && (react_1.default.createElement(ticket_row_1.TicketRow, { title: t('prices:kids'), priceHint: t('prices:childOnTop', {
|
|
346
|
+
amount: (0, format_amount_1.formatAmount)(tariff.child || 0, currency),
|
|
347
|
+
defaultValue: '+ {{amount}} за ребёнка, сверху к цене группы',
|
|
348
|
+
}), description: !hidePriceHints
|
|
349
|
+
? ((_f = selectedVariation === null || selectedVariation === void 0 ? void 0 : selectedVariation.childPriceDescription) === null || _f === void 0 ? void 0 : _f.trim()) || undefined
|
|
350
|
+
: undefined, value: _children, min: 0, max: maxChildren, minusLabel: t('prices:minus', { defaultValue: 'На одного меньше' }), plusLabel: t('prices:plus', { defaultValue: 'На одного больше' }), onChange: handleChangeChildren })),
|
|
351
|
+
!hidePriceHints && !flexSelected && !!totalHeads && (react_1.default.createElement(flex_columns_1.FlexColumns, { columns: 1, gr: 4, gc: 0 },
|
|
352
|
+
react_1.default.createElement(flex_1.Flex, { justify: 'start', gap: 'xs' },
|
|
353
|
+
react_1.default.createElement(InfoMarkS_1.default, { style: { fill: 'var(--color-text-secondary)' } }),
|
|
354
|
+
react_1.default.createElement(typography_1.Typography.Text, { view: 'caps', color: 'secondary' }, t('prices:totalPriceDescription', {
|
|
355
|
+
teamPrice: (0, format_amount_1.formatAmount)(teamPrice, currency),
|
|
356
|
+
personPrice: (0, format_amount_1.formatAmount)(personPrice, currency),
|
|
357
|
+
}))),
|
|
358
|
+
(0, reduced_price_1.getMinGameAmount)(reducedConfig) > 0 && (react_1.default.createElement(typography_1.Typography.Text, { view: 'caps', color: 'secondary' }, floorReached
|
|
359
|
+
? t('prices:floorReached', {
|
|
360
|
+
minAmount: minAmountLabel,
|
|
361
|
+
defaultValue: 'Уже минимум {{minAmount}} — лишние льготные места цену не снижают',
|
|
362
|
+
})
|
|
363
|
+
: t('prices:floorHint', {
|
|
364
|
+
minAmount: minAmountLabel,
|
|
365
|
+
defaultValue: 'Игра не стоит меньше {{minAmount}}',
|
|
366
|
+
}))))),
|
|
367
|
+
hidePriceHints && !flexSelected && !!totalHeads && (react_1.default.createElement("div", { style: {
|
|
373
368
|
display: 'flex',
|
|
374
369
|
flexDirection: 'column',
|
|
375
370
|
gap: 6,
|
|
@@ -378,21 +373,26 @@ const PlayersAndVariationPicker = ({ slot, currentTariffId, currentDuration, loa
|
|
|
378
373
|
borderRadius: 'calc(var(--widget-radius, 12px) * 0.75)',
|
|
379
374
|
boxSizing: 'border-box',
|
|
380
375
|
} },
|
|
381
|
-
|
|
376
|
+
react_1.default.createElement(flex_1.Flex, { justify: 'start', gap: 'xs' },
|
|
382
377
|
react_1.default.createElement(InfoMarkS_1.default, { style: { fill: 'var(--color-text-secondary)' } }),
|
|
383
378
|
react_1.default.createElement(typography_1.Typography.Text, { view: 'caps', color: 'secondary' }, t('prices:totalPriceDescription', {
|
|
384
379
|
teamPrice: (0, format_amount_1.formatAmount)(teamPrice, currency),
|
|
385
380
|
personPrice: (0, format_amount_1.formatAmount)(personPrice, currency),
|
|
386
|
-
})))
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
381
|
+
}))),
|
|
382
|
+
(0, reduced_price_1.getMinGameAmount)(reducedConfig) > 0 && (react_1.default.createElement(typography_1.Typography.Text, { view: 'caps', color: 'secondary' }, floorReached
|
|
383
|
+
? t('prices:floorReached', {
|
|
384
|
+
minAmount: minAmountLabel,
|
|
385
|
+
defaultValue: 'Уже минимум {{minAmount}} — лишние льготные места цену не снижают',
|
|
386
|
+
})
|
|
387
|
+
: t('prices:floorHint', {
|
|
388
|
+
minAmount: minAmountLabel,
|
|
389
|
+
defaultValue: 'Игра не стоит меньше {{minAmount}}',
|
|
390
|
+
}))))),
|
|
391
391
|
!!error && (react_1.default.createElement(toast_plate_1.ToastPlate, { view: 'negative' }, error || t('prices:playersError', { minPlayers, maxPlayers: effectiveMax }))),
|
|
392
392
|
!!onSave && (react_1.default.createElement(button_1.Button, { onClick: () => onSave({
|
|
393
393
|
players: _players,
|
|
394
394
|
children: _children,
|
|
395
|
-
reducedPlayers:
|
|
395
|
+
reducedPlayers: appliedReduced,
|
|
396
396
|
}), view: 'primary', size: 'xs' }, t('common:save')))));
|
|
397
397
|
};
|
|
398
398
|
exports.PlayersAndVariationPicker = PlayersAndVariationPicker;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
type Props = {
|
|
3
|
+
title: string;
|
|
4
|
+
priceHint: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
value: number;
|
|
7
|
+
min: number;
|
|
8
|
+
max: number;
|
|
9
|
+
minusLabel: string;
|
|
10
|
+
plusLabel: string;
|
|
11
|
+
onChange: (value: number) => void;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Строка спеццены: слева что это за места и сколько они снимают
|
|
15
|
+
* с группового тарифа, справа сколько таких мест в брони.
|
|
16
|
+
*/
|
|
17
|
+
export declare const TicketRow: React.FC<Props>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.TicketRow = void 0;
|
|
7
|
+
const MinusM_1 = __importDefault(require("@alphakits/icons/dist/MinusM"));
|
|
8
|
+
const PlusM_1 = __importDefault(require("@alphakits/icons/dist/PlusM"));
|
|
9
|
+
const typography_1 = require("@alphakits/ui/dist/typography");
|
|
10
|
+
const react_1 = __importDefault(require("react"));
|
|
11
|
+
const index_module_css_1 = __importDefault(require("./index.module.css"));
|
|
12
|
+
/**
|
|
13
|
+
* Строка спеццены: слева что это за места и сколько они снимают
|
|
14
|
+
* с группового тарифа, справа сколько таких мест в брони.
|
|
15
|
+
*/
|
|
16
|
+
const TicketRow = ({ title, priceHint, description, value, min, max, minusLabel, plusLabel, onChange, }) => {
|
|
17
|
+
const active = value > min;
|
|
18
|
+
return (react_1.default.createElement("div", { className: active ? `${index_module_css_1.default.ticket} ${index_module_css_1.default.ticketActive}` : index_module_css_1.default.ticket },
|
|
19
|
+
react_1.default.createElement("div", { className: index_module_css_1.default.body },
|
|
20
|
+
react_1.default.createElement(typography_1.Typography.Text, { view: 'primary-small', weight: 'medium' }, title),
|
|
21
|
+
react_1.default.createElement(typography_1.Typography.Text, { view: 'caps', color: 'secondary' }, priceHint),
|
|
22
|
+
!!description && (react_1.default.createElement(typography_1.Typography.Text, { view: 'caps', color: 'secondary' }, description))),
|
|
23
|
+
react_1.default.createElement("div", { className: index_module_css_1.default.stub },
|
|
24
|
+
react_1.default.createElement("button", { type: 'button', className: index_module_css_1.default.stepperButton, "aria-label": minusLabel, disabled: value <= min, onClick: () => onChange(Math.max(min, value - 1)) },
|
|
25
|
+
react_1.default.createElement(MinusM_1.default, null)),
|
|
26
|
+
react_1.default.createElement("span", { className: index_module_css_1.default.stepperValue }, value),
|
|
27
|
+
react_1.default.createElement("button", { type: 'button', className: index_module_css_1.default.stepperButton, "aria-label": plusLabel, disabled: value >= max, onClick: () => onChange(Math.min(max, value + 1)) },
|
|
28
|
+
react_1.default.createElement(PlusM_1.default, null)))));
|
|
29
|
+
};
|
|
30
|
+
exports.TicketRow = TicketRow;
|
|
@@ -25,10 +25,9 @@ export type PickerSlotVariation = {
|
|
|
25
25
|
};
|
|
26
26
|
childPriceDescription?: string | null;
|
|
27
27
|
/**
|
|
28
|
-
* Минимальная стоимость игры и льготная ставка тарифа.
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* UI и броней.
|
|
28
|
+
* Минимальная стоимость игры и льготная ставка тарифа. Пикер и бэк считают
|
|
29
|
+
* цену одной формулой: льготное место снимает ставку с группового тарифа,
|
|
30
|
+
* итог не ниже минимума. Число мест гость выбирает сам, до размера группы.
|
|
32
31
|
*/
|
|
33
32
|
minGameAmount?: number;
|
|
34
33
|
reducedRateMode?: TariffReducedRateModeEnum | null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@escapenavigator/hooks",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.49",
|
|
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.0.
|
|
18
|
-
"@escapenavigator/types": "^2.0.
|
|
19
|
-
"@escapenavigator/utils": "^2.0.
|
|
17
|
+
"@escapenavigator/services": "^2.0.49",
|
|
18
|
+
"@escapenavigator/types": "^2.0.47",
|
|
19
|
+
"@escapenavigator/utils": "^2.0.49",
|
|
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": "9eaab88a1509930c7715945ce519eaeba879f550"
|
|
36
36
|
}
|