@redneckz/wildless-cms-uni-blocks 0.14.246 → 0.14.248

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.
Files changed (121) hide show
  1. package/bundle/blocks.schema.json +1 -1
  2. package/bundle/bundle.umd.js +1197 -1159
  3. package/bundle/bundle.umd.min.js +1 -1
  4. package/bundle/components/BankAccountFiltration/BankAccountFiltration.d.ts +6 -0
  5. package/bundle/components/BankAccountFiltration/BankAccountFiltrationContent.d.ts +21 -0
  6. package/bundle/components/Carousel/Carousel.d.ts +2 -1
  7. package/bundle/components/CarouselLinks/CarouselLinks.d.ts +2 -1
  8. package/bundle/ui-kit/BaseFiltration/BaseFiltrationContent.d.ts +4 -0
  9. package/{lib/ui-kit/BaseFiltration/renderFiltrationForm.d.ts → bundle/ui-kit/BaseFiltration/FiltrationForm.d.ts} +4 -6
  10. package/dist/components/BankAccountFiltration/BankAccountFiltration.d.ts +6 -0
  11. package/dist/components/BankAccountFiltration/BankAccountFiltration.js +50 -0
  12. package/dist/components/BankAccountFiltration/BankAccountFiltration.js.map +1 -0
  13. package/dist/components/BankAccountFiltration/BankAccountFiltrationContent.d.ts +21 -0
  14. package/dist/components/BankAccountFiltration/BankAccountFiltrationContent.js +2 -0
  15. package/dist/components/BankAccountFiltration/BankAccountFiltrationContent.js.map +1 -0
  16. package/dist/components/Blocks.js +2 -0
  17. package/dist/components/Blocks.js.map +1 -1
  18. package/dist/components/Carousel/Carousel.d.ts +2 -1
  19. package/dist/components/CarouselLinks/CarouselLinks.d.ts +2 -1
  20. package/dist/components/DepositFiltration/DepositFiltration.js +5 -6
  21. package/dist/components/DepositFiltration/DepositFiltration.js.map +1 -1
  22. package/dist/components/MortgageFiltration/MortgageFiltration.js +2 -2
  23. package/dist/components/MortgageFiltration/MortgageFiltration.js.map +1 -1
  24. package/dist/ui-kit/BaseFiltration/BaseFiltrationContent.d.ts +4 -0
  25. package/{bundle/ui-kit/BaseFiltration/renderFiltrationForm.d.ts → dist/ui-kit/BaseFiltration/FiltrationForm.d.ts} +4 -7
  26. package/dist/ui-kit/BaseFiltration/FiltrationForm.js +28 -0
  27. package/dist/ui-kit/BaseFiltration/FiltrationForm.js.map +1 -0
  28. package/lib/common.css +1 -1
  29. package/lib/components/BankAccountFiltration/BankAccountFiltration.d.ts +6 -0
  30. package/lib/components/BankAccountFiltration/BankAccountFiltration.fixture.d.ts +6 -0
  31. package/lib/components/BankAccountFiltration/BankAccountFiltration.js +48 -0
  32. package/lib/components/BankAccountFiltration/BankAccountFiltration.js.map +1 -0
  33. package/lib/components/BankAccountFiltration/BankAccountFiltrationContent.d.ts +21 -0
  34. package/lib/components/BankAccountFiltration/BankAccountFiltrationContent.js +2 -0
  35. package/lib/components/BankAccountFiltration/BankAccountFiltrationContent.js.map +1 -0
  36. package/lib/components/Blocks.js +2 -0
  37. package/lib/components/Blocks.js.map +1 -1
  38. package/lib/components/Carousel/Carousel.d.ts +2 -1
  39. package/lib/components/CarouselLinks/CarouselLinks.d.ts +2 -1
  40. package/lib/components/DepositFiltration/DepositFiltration.js +5 -6
  41. package/lib/components/DepositFiltration/DepositFiltration.js.map +1 -1
  42. package/lib/components/MortgageFiltration/MortgageFiltration.js +2 -2
  43. package/lib/components/MortgageFiltration/MortgageFiltration.js.map +1 -1
  44. package/lib/ui-kit/BaseFiltration/BaseFiltrationContent.d.ts +4 -0
  45. package/{dist/ui-kit/BaseFiltration/renderFiltrationForm.d.ts → lib/ui-kit/BaseFiltration/FiltrationForm.d.ts} +4 -7
  46. package/lib/ui-kit/BaseFiltration/FiltrationForm.js +26 -0
  47. package/lib/ui-kit/BaseFiltration/FiltrationForm.js.map +1 -0
  48. package/mobile/bundle/bundle.umd.js +15 -12
  49. package/mobile/bundle/bundle.umd.min.js +1 -1
  50. package/mobile/bundle/components/BankAccountFiltration/BankAccountFiltration.d.ts +6 -0
  51. package/mobile/bundle/components/BankAccountFiltration/BankAccountFiltrationContent.d.ts +21 -0
  52. package/mobile/bundle/components/Carousel/Carousel.d.ts +2 -1
  53. package/mobile/bundle/components/CarouselLinks/CarouselLinks.d.ts +2 -1
  54. package/mobile/bundle/ui-kit/BaseFiltration/BaseFiltrationContent.d.ts +4 -0
  55. package/mobile/bundle/ui-kit/BaseFiltration/{renderFiltrationForm.d.ts → FiltrationForm.d.ts} +4 -7
  56. package/mobile/dist/components/BankAccountFiltration/BankAccountFiltration.d.ts +6 -0
  57. package/mobile/dist/components/BankAccountFiltration/BankAccountFiltration.js +50 -0
  58. package/mobile/dist/components/BankAccountFiltration/BankAccountFiltration.js.map +1 -0
  59. package/mobile/dist/components/BankAccountFiltration/BankAccountFiltrationContent.d.ts +21 -0
  60. package/mobile/dist/components/BankAccountFiltration/BankAccountFiltrationContent.js +2 -0
  61. package/mobile/dist/components/BankAccountFiltration/BankAccountFiltrationContent.js.map +1 -0
  62. package/mobile/dist/components/Carousel/Carousel.d.ts +2 -1
  63. package/mobile/dist/components/CarouselLinks/CarouselLinks.d.ts +2 -1
  64. package/mobile/dist/components/DepositFiltration/DepositFiltration.js +5 -6
  65. package/mobile/dist/components/DepositFiltration/DepositFiltration.js.map +1 -1
  66. package/mobile/dist/components/MortgageFiltration/MortgageFiltration.js +2 -2
  67. package/mobile/dist/components/MortgageFiltration/MortgageFiltration.js.map +1 -1
  68. package/mobile/dist/ui-kit/BaseFiltration/BaseFiltrationContent.d.ts +4 -0
  69. package/mobile/dist/ui-kit/BaseFiltration/FiltrationForm.d.ts +12 -0
  70. package/mobile/dist/ui-kit/BaseFiltration/FiltrationForm.js +28 -0
  71. package/mobile/dist/ui-kit/BaseFiltration/FiltrationForm.js.map +1 -0
  72. package/mobile/lib/common.css +1 -1
  73. package/mobile/lib/components/BankAccountFiltration/BankAccountFiltration.d.ts +6 -0
  74. package/mobile/lib/components/BankAccountFiltration/BankAccountFiltration.js +48 -0
  75. package/mobile/lib/components/BankAccountFiltration/BankAccountFiltration.js.map +1 -0
  76. package/mobile/lib/components/BankAccountFiltration/BankAccountFiltrationContent.d.ts +21 -0
  77. package/mobile/lib/components/BankAccountFiltration/BankAccountFiltrationContent.js +2 -0
  78. package/mobile/lib/components/BankAccountFiltration/BankAccountFiltrationContent.js.map +1 -0
  79. package/mobile/lib/components/Carousel/Carousel.d.ts +2 -1
  80. package/mobile/lib/components/CarouselLinks/CarouselLinks.d.ts +2 -1
  81. package/mobile/lib/components/DepositFiltration/DepositFiltration.js +5 -6
  82. package/mobile/lib/components/DepositFiltration/DepositFiltration.js.map +1 -1
  83. package/mobile/lib/components/MortgageFiltration/MortgageFiltration.js +2 -2
  84. package/mobile/lib/components/MortgageFiltration/MortgageFiltration.js.map +1 -1
  85. package/mobile/lib/ui-kit/BaseFiltration/BaseFiltrationContent.d.ts +4 -0
  86. package/mobile/lib/ui-kit/BaseFiltration/FiltrationForm.d.ts +12 -0
  87. package/mobile/lib/ui-kit/BaseFiltration/FiltrationForm.js +26 -0
  88. package/mobile/lib/ui-kit/BaseFiltration/FiltrationForm.js.map +1 -0
  89. package/mobile/src/components/BankAccountFiltration/BankAccountFiltration.example.json +64 -0
  90. package/mobile/src/components/BankAccountFiltration/BankAccountFiltration.tsx +109 -0
  91. package/mobile/src/components/BankAccountFiltration/BankAccountFiltration.ui.json +1 -0
  92. package/mobile/src/components/BankAccountFiltration/BankAccountFiltrationContent.ts +27 -0
  93. package/mobile/src/components/Carousel/Carousel.tsx +1 -1
  94. package/mobile/src/components/CarouselLinks/CarouselLinks.tsx +1 -1
  95. package/mobile/src/components/DepositFiltration/DepositFiltration.tsx +25 -22
  96. package/mobile/src/components/MortgageFiltration/MortgageFiltration.tsx +7 -2
  97. package/mobile/src/ui-kit/BaseFiltration/BaseFiltrationContent.ts +4 -0
  98. package/mobile/src/ui-kit/BaseFiltration/{renderFiltrationForm.tsx → FiltrationForm.tsx} +31 -26
  99. package/package.json +1 -1
  100. package/src/components/BankAccountFiltration/BankAccountFiltration.example.json +64 -0
  101. package/src/components/BankAccountFiltration/BankAccountFiltration.fixture.tsx +141 -0
  102. package/src/components/BankAccountFiltration/BankAccountFiltration.tsx +109 -0
  103. package/src/components/BankAccountFiltration/BankAccountFiltration.ui.json +1 -0
  104. package/src/components/BankAccountFiltration/BankAccountFiltrationContent.ts +27 -0
  105. package/src/components/Blocks.ts +2 -0
  106. package/src/components/Carousel/Carousel.tsx +1 -1
  107. package/src/components/CarouselLinks/CarouselLinks.tsx +1 -1
  108. package/src/components/DepositFiltration/DepositFiltration.tsx +25 -22
  109. package/src/components/MortgageFiltration/MortgageFiltration.tsx +7 -2
  110. package/src/ui-kit/BaseFiltration/BaseFiltrationContent.ts +4 -0
  111. package/src/ui-kit/BaseFiltration/{renderFiltrationForm.tsx → FiltrationForm.tsx} +31 -26
  112. package/dist/ui-kit/BaseFiltration/renderFiltrationForm.js +0 -23
  113. package/dist/ui-kit/BaseFiltration/renderFiltrationForm.js.map +0 -1
  114. package/lib/ui-kit/BaseFiltration/renderFiltrationForm.js +0 -20
  115. package/lib/ui-kit/BaseFiltration/renderFiltrationForm.js.map +0 -1
  116. package/mobile/dist/ui-kit/BaseFiltration/renderFiltrationForm.d.ts +0 -15
  117. package/mobile/dist/ui-kit/BaseFiltration/renderFiltrationForm.js +0 -23
  118. package/mobile/dist/ui-kit/BaseFiltration/renderFiltrationForm.js.map +0 -1
  119. package/mobile/lib/ui-kit/BaseFiltration/renderFiltrationForm.d.ts +0 -15
  120. package/mobile/lib/ui-kit/BaseFiltration/renderFiltrationForm.js +0 -20
  121. package/mobile/lib/ui-kit/BaseFiltration/renderFiltrationForm.js.map +0 -1
@@ -1849,60 +1849,109 @@
1849
1849
  .filter((input) => !input?.condition ||
1850
1850
  input?.condition.values.find((_) => _ === data?.[input?.condition?.name]));
1851
1851
 
1852
- /** @deprecated */
1853
- const AlignStyles = 'text-center';
1852
+ const joinList = (sep) => (list) => list.reduce((acc, el, i) => (acc.length ? acc.concat({ ...sep, key: `sep-${i}` }, el) : [el]), []);
1854
1853
 
1855
- const iconBgStyles = {
1856
- '': 'bg-main-divider group-data-secondary:bg-white/30',
1857
- primary: 'bg-main-divider',
1858
- secondary: 'bg-secondary-light',
1859
- opacity: 'bg-white/30',
1860
- };
1861
- const RoundedIcon = JSX(({ className, defaultIconSize = 44, iconBgVersion = 'primary', size, icon, ...rest }) => {
1862
- const widthAndHeight = String(size?.width || defaultIconSize);
1863
- return icon ? (jsx("div", { style: {
1864
- width: `${widthAndHeight}px`,
1865
- height: `${widthAndHeight}px`,
1866
- }, className: style('rounded-icon object-fit flex justify-center items-center', iconBgStyles[iconBgVersion], className), children: jsx(Icon, { ...rest, width: "54%", height: "54%", name: icon }) })) : null;
1854
+ const SlideCheckboxGroup = JSX(({ value, onChange, items = [], multiple }) => {
1855
+ const handleChange = (newValue) => {
1856
+ if (!onChange) {
1857
+ return;
1858
+ }
1859
+ const hasAlready = value?.includes(newValue);
1860
+ if (multiple) {
1861
+ onChange(hasAlready ? (value || []).filter((v) => v !== newValue) : [...(value || []), newValue]);
1862
+ }
1863
+ else {
1864
+ onChange(hasAlready ? [] : [newValue]);
1865
+ }
1866
+ };
1867
+ return items.map((item) => (jsx(SlideCheckbox, { text: item?.title, value: [value].flatMap((_) => _).includes(item.value), onChange: () => handleChange(item.value), type: multiple ? 'checkbox' : 'radio' }, item.value)));
1867
1868
  });
1868
1869
 
1869
- const defaultIconSize = 44;
1870
-
1871
- const BenefitsBlock = JSX(({ className = '', title, benefitList, ...rest }) => (jsxs(BlockWrapper, { className: className, defaultPadding: "p-6xl", ...rest, children: [title ? jsx(Heading, { headingType: "h3", as: "h2", className: AlignStyles, title: title }) : null, benefitList?.length ? (jsx("div", { className: "flex flex-col @md:flex-row flex-wrap w-full @md:justify-center mt-xl gap-5xl", role: "list", children: benefitList.map(renderBenefit) })) : null] })));
1872
- const renderBenefit = ({ label, description, items, icon, iconBgVersion, isDotted = false }, i) => (jsxs("div", { className: "flex gap-m items-center @md:basis-1/2 @md:max-w-[500px]", role: "listitem", children: [icon?.icon ? (jsx(RoundedIcon, { className: "shrink-0", defaultIconSize: defaultIconSize, iconBgVersion: iconBgVersion || '', ...icon })) : null, icon?.src ? jsx(Img, { className: "shrink-0", image: icon }) : null, label ? (jsxs("div", { className: "@sm:h-full space-y-xs", children: [jsx(Heading, { headingType: "h6", children: label }), jsx(Text, { size: "text-l", color: "text-secondary-text group-data-secondary:text-white/80", font: "font-light", children: description }), jsx(List, { dotClassName: "bg-secondary-text", listItemSize: "S", items: items, isDotted: isDotted })] })) : null] }, String(i)));
1870
+ const FiltrationForm = JSX(({ children, filtrationSchema, resetButton, field, reset }) => (jsxs("form", { className: "flex flex-col @4xl:flex-row justify-between bg-white p-4xl gap-s @4xl:gap-0", children: [jsxs("div", { className: "flex flex-col @4xl:flex-row gap-xl", children: [children, joinList(jsx("div", { className: "h-full w-px bg-gray" }))(
1871
+ // TS can't correctly refine array's type after filter
1872
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
1873
+ Object.entries(filtrationSchema).map(renderFormItem(field)).filter(Boolean))] }), resetButton ? (jsx("div", { children: jsx(Button, { type: "reset", className: "w-full @4xl:w-56 @4xl:ml-s", onClick: reset, children: resetButton?.text }) })) : null] })));
1874
+ const renderFormItem = (field) => ([key, item]) => {
1875
+ const type = item?.type;
1876
+ switch (type) {
1877
+ case 'array':
1878
+ return renderArrayFormItem(key, field)(item);
1879
+ default:
1880
+ return null;
1881
+ }
1882
+ };
1883
+ const renderArrayFormItem = (key, field) => (item) => item?.ui !== 'dropdown' ? renderCheckboxes(key, field)(item) : null;
1884
+ const renderCheckboxes = (key, field) => (item) => {
1885
+ const options = item?.items?.enum?.map((e, i) => ({ value: e, title: item?.items?.enumNames?.[i] || '' })) ||
1886
+ [];
1887
+ return (jsxs("div", { className: "flex-1", children: [item?.title ? (jsx("div", { className: "mb-m", children: jsx(Text, { size: "text-m", font: "font-medium", color: "text-primary-text", children: item.title }) })) : null, jsx(SlideCheckboxGroup, { items: options, multiple: !item?.single, ...field(key) })] }, key));
1888
+ };
1873
1889
 
1874
- const bonusBenefitStyles = 'items-center';
1890
+ function zip(firstCol, ...restCols) {
1891
+ return firstCol.map((firstEl, rowIndex) => [firstEl, ...restCols.map((col) => col[rowIndex])]);
1892
+ }
1875
1893
 
1876
- const renderBonusBenefit = (cardTextAlign) => ({ icon, title, description }, i) => (jsxs("div", { className: style('flex flex-col gap-1', bonusBenefitStyles, !description && !title ? 'justify-center' : ''), children: [title ? (jsx(Text, { size: "text-h6", font: "font-normal", align: cardTextAlign, children: title })) : null, icon ? jsx(Img, { image: icon, height: "180", className: "w-auto mx-auto" }) : null, description ? (jsx(Text, { size: "text-h6", font: "font-normal", align: cardTextAlign, children: description })) : null] }, String(i)));
1894
+ const isEmpty = (v) => v === null ||
1895
+ v === undefined ||
1896
+ v === '' ||
1897
+ (Array.isArray(v) && v.length === 0) ||
1898
+ (typeof v === 'object' && Object.keys(v).length === 0);
1877
1899
 
1878
- const renderBonusBenefits = (bonusBenefits, columnsCount, cardTextAlign) => (jsx("div", { className: `grid gap-2xs ${getElementsColsValue(columnsCount)}`, children: bonusBenefits.map(renderBonusBenefit(cardTextAlign)) }));
1879
- const getElementsColsValue = (colValue) => {
1880
- switch (colValue) {
1881
- case '3':
1882
- return 'grid-cols-3';
1883
- case '4':
1884
- return 'grid-cols-4';
1885
- case '5':
1886
- return 'grid-cols-5';
1887
- default:
1888
- return 'grid-cols-5';
1900
+ const isSingularFieldMatch = (key, pattern) => (target) => isEmpty(pattern[key]) || isEmpty(target[key]) || pattern[key] === target[key];
1901
+ const isMultipleFieldMatch = (key, pattern) => (target) => isEmpty(pattern[key]) ||
1902
+ isEmpty(target[key]) ||
1903
+ pattern[key]?.every((_) => target[key]?.includes(_));
1904
+ const isNumberFieldMatch = (key, pattern) => (target) => isEmpty(pattern[key]) ||
1905
+ isEmpty(target[key]) ||
1906
+ naturalNumber(pattern[key]) <= naturalNumber(target[key]);
1907
+ const isMultipleNumberFieldMatch = (key, pattern) => (target) => isEmpty(pattern[key]) || isEmpty(target[key]) || target[key]?.includes(pattern[key]);
1908
+ const isValueInRange = (key, pattern) => (target) => isEmpty(pattern[key]) ||
1909
+ isEmpty(target[key]) ||
1910
+ (naturalNumber(pattern[key]?.min) >= naturalNumber(target[key]?.min) &&
1911
+ naturalNumber(pattern[key]?.min) <= naturalNumber(target[key]?.max));
1912
+ const targetMatchPattern = (pattern, target) => Object.entries(pattern)
1913
+ .map(([key, subPattern]) => {
1914
+ if (typeof subPattern === 'number') {
1915
+ return Array.isArray(target[key])
1916
+ ? isMultipleNumberFieldMatch(key, pattern)
1917
+ : isNumberFieldMatch(key, pattern);
1889
1918
  }
1890
- };
1919
+ if (typeof subPattern === 'object' && 'min' in subPattern && 'max' in subPattern) {
1920
+ return isValueInRange(key, pattern);
1921
+ }
1922
+ return Array.isArray(subPattern)
1923
+ ? isMultipleFieldMatch(key, pattern)
1924
+ : isSingularFieldMatch(key, pattern);
1925
+ })
1926
+ .every((predicate) => predicate(target));
1927
+ const naturalNumber = (n) => Number(n || 0);
1891
1928
 
1892
- const BonusBenefitsBlock = JSX(({ bonusBenefits, cardTextAlign = 'text-center', className = '', columnsCount = '4', subtitle, title, ...rest }) => (jsxs(BlockWrapper, { className: style('space-y-3xl', className), defaultPadding: "p-6xl", ...rest, children: [jsx(Headline, { defaultPadding: "", title: title, description: subtitle, headlineVersion: "M" }), bonusBenefits?.length
1893
- ? renderBonusBenefits(bonusBenefits, columnsCount, cardTextAlign)
1894
- : null] })));
1929
+ const filterBlocks = (blocksWithProps, filtrationState) => (normalize = identity) => blocksWithProps
1930
+ ?.filter(([, content]) => targetMatchPattern(filtrationState, normalize(content) || {}))
1931
+ .map(([block]) => block);
1895
1932
 
1896
- const AlignJustifyStyle = {
1897
- start: 'justify-start',
1898
- center: 'justify-center',
1899
- end: 'justify-end',
1933
+ const useFiltrationForm = (initialFormState, { block, page, normalize = identity, }) => {
1934
+ const blocks = block?.blocks || [];
1935
+ const [filtrationState, { field, reset, update: replaceFiltrationState }] = useForm(initialFormState);
1936
+ const blocksWithProps = useMemo(() => zip(blocks, blocks.map((b) => resolveBlockProps(b.content, page?.fallback))), [blocks, page?.fallback]);
1937
+ const blocksToRender = filtrationState === initialFormState
1938
+ ? blocks
1939
+ : filterBlocks(blocksWithProps, filtrationState)(normalize);
1940
+ return [
1941
+ filtrationState,
1942
+ { blocksToRender, blocksWithProps },
1943
+ { field, reset, update: replaceFiltrationState },
1944
+ ];
1900
1945
  };
1901
1946
 
1902
- const ButtonsBlock = JSX(({ buttons, className = '', align, ...rest }) => {
1903
- const alignStyle = align ? style('flex', AlignJustifyStyle[align]) : '';
1904
- return (jsx(BlockWrapper, { className: style('!bg-transparent', alignStyle, className), defaultPadding: "p-0", ...rest, children: renderButtonsSection(buttons) }));
1905
- });
1947
+ /** @deprecated */
1948
+ const AlignStyles = 'text-center';
1949
+
1950
+ const NotFound = JSX(({ title, description }) => (jsxs("div", { className: "bg-white p-6xl py-20 flex gap-s flex-col @xl:items-center", children: [jsx(Img, { image: { icon: 'MagnifierIcon', iconVersion: 'normal' }, width: "120", height: "120" }), jsx(Headline, { title: title, description: description, headlineVersion: "S", isEmbedded: true, align: AlignStyles })] })));
1951
+
1952
+ const getBorderStyle = (showBorder = false) => showBorder
1953
+ ? 'border border-solid border-gray group-data-secondary:border-white/50 group-data-gray:border-main-divider'
1954
+ : '';
1906
1955
 
1907
1956
  const useCarouselControls = (props, initialIndex = 0) => {
1908
1957
  const { itemCount, visibleItemCount } = props;
@@ -1926,211 +1975,119 @@
1926
1975
  ];
1927
1976
  };
1928
1977
 
1929
- /** @deprecated */
1930
- const ArrowButton = JSX(({ className, disabled, ...rest }) => (jsx(LinkButton, { rounded: true, className: style('group/btn w-12 h-12 min-h-12 max-h-12 flex items-center justify-center shadow-main-black', disabled
1931
- ? 'bg-secondary-light text-secondary-text'
1932
- : 'bg-white text-primary-text hover:text-primary-main', className || ''), disabled: disabled, ...rest, children: jsx(Img, { className: "w-4 h-4 min-w-4 min-h-4 rounded-full box-border", image: { icon: 'ArrowLeftIcon', iconVersion: 'black' }, imageClassName: "group-hover/btn:text-primary-main", width: "16", height: "16" }) })));
1933
- /** @deprecated */
1934
- const ScrollLeftButton = JSX((props) => (jsx(ArrowButton, { ...props, ariaLabel: "\u041F\u0440\u043E\u043B\u0438\u0441\u0442\u0430\u0442\u044C \u0432\u043B\u0435\u0432\u043E", "data-block-control": "scroll-left" })));
1935
- /** @deprecated */
1936
- const ScrollRightButton = JSX((props) => (jsx(ArrowButton, { ...props, className: `${props.className || ''} rotate-180`, ariaLabel: "\u041F\u0440\u043E\u043B\u0438\u0441\u0442\u0430\u0442\u044C \u0432\u043F\u0440\u0430\u0432\u043E", "data-block-control": "scroll-right" })));
1937
-
1938
- /** @deprecated */
1939
- function renderArrows({ dec, inc, canDec, canInc, btnClass, className = '', }) {
1940
- const isVisible = canDec || canInc;
1941
- if (!isVisible) {
1942
- return null;
1978
+ const mapChildren = (mapper) => (children) => {
1979
+ if (children === null || children === undefined) {
1980
+ return children;
1943
1981
  }
1944
- return (jsxs("div", { children: [canDec ? (jsx(ArrowButton, { className: style('absolute z-40', btnClass[0], className), onClick: dec, ariaLabel: "\u041F\u0440\u043E\u043B\u0438\u0441\u0442\u0430\u0442\u044C \u0432\u043B\u0435\u0432\u043E", "data-block-control": "scroll-left", disabled: !canDec })) : null, canInc ? (jsx(ArrowButton, { className: style('absolute z-40 rotate-180', btnClass[1], className), onClick: inc, ariaLabel: "\u041F\u0440\u043E\u043B\u0438\u0441\u0442\u0430\u0442\u044C \u0432\u043F\u0440\u0430\u0432\u043E", "data-block-control": "scroll-right", disabled: !canInc })) : null] }));
1945
- }
1946
-
1947
- const calculatorValueBlockStyle = 'flex justify-between @xl:flex-col';
1948
- const CalculatorValueBlock = JSX(({ className, title, value, fractionDigits, fixed, prefix, postfix }) => (jsxs("div", { className: style(calculatorValueBlockStyle, className), children: [title ? (jsx(Text, { size: "text-l", font: "font-light", color: "text-secondary-text", children: title })) : null, value ? (jsx("div", { children: jsx(Text, { size: "text-h3", children: [prefix, toLocalNumberFormat(fractionDigits, { fixed })(value), postfix]
1949
- .filter(Boolean)
1950
- .join(' ') }) })) : (jsx("div", { children: jsx(Text, { size: "text-h3", children: "-" }) }))] })));
1951
-
1952
- const DEFAULT_MIN_SUM$1 = 10000;
1953
- const DEFAULT_MAX_SUM$1 = 5000000;
1954
- const CREDIT_DEFAULT_SUM = 200000;
1955
- const REFINANCING_DEFAULT_SUM = 700000;
1956
- const REFINANCING_DEFAULT_MAX_SUM = 3000000;
1957
- const REFINANCING_OLD_MIN_SUM = 0;
1958
- const REFINANCING_OLD_DEFAULT_SUM = 0;
1959
- const REFINANCING_OLD_MAX_SUM = 2500000;
1960
- const REFINANCING_EXTRA_MIN_SUM = 0;
1961
- const REFINANCING_EXTRA_MAX_SUM = 500000;
1962
- const DEPOSIT_DEFAULT_SUM = 20000;
1963
- const DEFAULT_MIN_MONTHS = 1;
1964
- const DEFAULT_REFINANCING_MIN_MONTHS = 13;
1965
- const DEFAULT_REFINANCING_MONTHS = 48;
1966
- const DEFAULT_MAX_MONTHS = 60;
1967
- const DEFAULT_MONTHS = Math.round((DEFAULT_MAX_MONTHS - DEFAULT_MIN_MONTHS) / 2);
1968
- const BUSINESS_DEFAULT_SUM = 100000;
1969
- const BUSINESS_MIN_DAYS = 1;
1970
- const BUSINESS_MAX_DAYS = 1095;
1971
- const BUSINESS_RATE_MIN_DAYS = 30; // порог количества дней, при которых ставка не доступна
1972
- const BUSINESS_OPERATING_MIN_DAYS = 90;
1973
- const BUSINESS_OPERATING_MAX_DAYS = 730;
1974
- const BUSINESS_DEFAULT_DAYS = 60;
1975
- const MONTHS_IN_YEAR = 12;
1976
- const MONTHLY_INTEREST_RATE = MONTHS_IN_YEAR * 100;
1977
- const DEFAULT_CREDIT_CALCULATOR_PARAMS = {
1978
- minSum: DEFAULT_MIN_SUM$1,
1979
- maxSum: DEFAULT_MAX_SUM$1,
1980
- minMonths: DEFAULT_MIN_MONTHS,
1981
- maxMonths: DEFAULT_MAX_MONTHS,
1982
- rate: 0,
1983
- isSalaryClient: false,
1984
- isPensionClient: false,
1985
- isStateEmployee: false,
1986
- isInsurance: true,
1987
- };
1988
- const DEFAULT_REFINANCING_CALCULATOR_PARAMS = {
1989
- minSum: DEFAULT_MIN_SUM$1,
1990
- maxSum: REFINANCING_DEFAULT_MAX_SUM,
1991
- minSumOld: REFINANCING_OLD_MIN_SUM,
1992
- maxSumOld: REFINANCING_OLD_MAX_SUM,
1993
- minSumExtra: REFINANCING_EXTRA_MIN_SUM,
1994
- maxSumExtra: REFINANCING_EXTRA_MAX_SUM,
1995
- minMonths: DEFAULT_REFINANCING_MIN_MONTHS,
1996
- maxMonths: DEFAULT_MAX_MONTHS,
1997
- rate: 0,
1998
- isExtraMoney: false,
1999
- isSalaryClient: false,
2000
- isInsurance: true,
2001
- };
2002
- const DEFAULT_TRAVEL_MIN_SUM = 6000;
2003
- const DEFAULT_REST_MIN_SUM = 6000;
2004
- const DEFAULT_BONUS_CALCULATOR_PARAMS = {
2005
- minSumTravel: 6000,
2006
- maxSumTravel: 200000,
2007
- minSumOther: 6000,
2008
- maxSumOther: 200000,
2009
- maxBonus: 5000,
2010
- prefMonthsNum: 2,
2011
- prefTravelBonusRate: 0.015,
2012
- prefRestBonusRate: 0.01,
2013
- travelBonusRate: 0.015,
2014
- restBonusRate: 0.01,
2015
- };
2016
- const DEFAULT_DEPOSIT_CALCULATOR_PARAMS = {
2017
- minSum: 50000,
2018
- maxSum: 100000001,
2019
- minMonths: 2,
2020
- maxMonths: 10,
2021
- rate: 0,
2022
- isMonthlyInterestPayment: true,
2023
- isRemoteRegistration: true,
2024
- };
2025
- const DEFAULT_BUSINESS_DEPOSIT_CALCULATOR_PARAMS = {
2026
- minSum: 1000,
2027
- maxSum: 100000001,
2028
- minDays: BUSINESS_MIN_DAYS,
2029
- maxDays: BUSINESS_MAX_DAYS,
2030
- rate: 0,
2031
- isMonthlyInterestPayment: true,
2032
- isReplenished: false,
2033
- isPartialWithdrawal: false,
2034
- depositeName: "Депозит 'Стабильный'",
1982
+ return Array.isArray(children) ? children.map(mapper) : mapper(children, 0);
2035
1983
  };
2036
1984
 
2037
- const getBonus = (params, bonusTravelValue, bonusOtherValue) => {
2038
- const { maxBonus = 5000, prefMonthsNum = DEFAULT_BONUS_CALCULATOR_PARAMS.prefMonthsNum, prefTravelBonusRate = DEFAULT_BONUS_CALCULATOR_PARAMS.prefTravelBonusRate, prefRestBonusRate = 0.01, travelBonusRate = 0.015, restBonusRate = 0.01, } = params;
2039
- const prefBonus = Math.min(Math.round(prefTravelBonusRate * bonusTravelValue) +
2040
- Math.round(prefRestBonusRate * bonusOtherValue), maxBonus);
2041
- const bonus = Math.min(Math.round(travelBonusRate * bonusTravelValue) + Math.round(restBonusRate * bonusOtherValue), maxBonus);
2042
- const yearBonus = prefBonus * prefMonthsNum + bonus * (MONTHS_IN_YEAR - prefMonthsNum);
2043
- return [prefBonus, yearBonus];
2044
- };
1985
+ const HEADER_HEIGHT = 170;
1986
+ const FOOTER_HEIGHT = 48;
1987
+ const BLOCKS_GAP = 4;
1988
+ const PAGE_PADDINGS_X = 20;
1989
+ const DEFAULT_HEIGHT = 866;
1990
+ const getContainerHeight = (minHeight) => ({
1991
+ height: `calc(100vh - ${HEADER_HEIGHT + FOOTER_HEIGHT + BLOCKS_GAP + PAGE_PADDINGS_X}px)`,
1992
+ minHeight: `${minHeight || DEFAULT_HEIGHT}px`,
1993
+ });
2045
1994
 
2046
- const getBonusCalculatorParams = (userInputParams, calcSource) => {
2047
- const { travelExpenseValue, restExpenseValue } = userInputParams;
2048
- const [monthBonus, yearBonus] = getBonus(calcSource || DEFAULT_BONUS_CALCULATOR_PARAMS, travelExpenseValue, restExpenseValue);
2049
- return {
2050
- ...calcSource,
2051
- ...userInputParams,
2052
- monthBonus,
2053
- yearBonus,
2054
- };
2055
- };
2056
-
2057
- const renderFootnote = (text) => text ? (jsx(Paragraph, { size: "text-s", font: "font-light", color: "text-secondary-text", children: text })) : null;
2058
-
2059
- const Rate = JSX(({ rate = 0, title = 'Ставка', unit, fractionDigits = 2, depositeName, isShowDepositeName }) => (jsxs("div", { children: [jsx(Text, { size: "text-l", font: "font-light", color: "text-secondary-text", children: title }), jsxs("div", { className: "relative", children: [jsxs("div", { className: "flex justify-start gradient-color-text", children: [jsx("span", { className: "font-mohave text-title-huge pr-xl -mt-m tracking-[-15px]", children: toLocalNumberFormat(fractionDigits, { fixed: true })(rate) }), unit ? jsx("span", { className: "text-7xl", children: unit }) : null] }), depositeName && isShowDepositeName ? (jsx("div", { className: "absolute bottom-2 w-full text-left whitespace-nowrap", children: depositeName })) : null] })] })));
2060
-
2061
- const UnknownRate = JSX(({ title = 'Ставка', isUnavailableByTerm = false }) => (jsxs("div", { className: "space-y-s", children: [jsx(Text, { size: "text-l", font: "font-light", color: "text-secondary-text", children: title }), jsx(Icon, { className: "w-10 h-10 @5xl:h-auto @5xl:w-40", name: "PercentIcon" }), jsx("div", { className: "w-64", children: isUnavailableByTerm ? (jsxs(Paragraph, { size: "text-xl", font: "font-light", color: "text-primary-text", children: ["\u041F\u0440\u043E\u0446\u0435\u043D\u0442\u043D\u0430\u044F \u0441\u0442\u0430\u0432\u043A\u0430 \u0434\u043B\u044F \u0441\u0440\u043E\u043A\u0430 ", jsx("strong", { children: "1-30 \u0434\u043D\u0435\u0439" }), " \u0443\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0435\u0442\u0441\u044F \u043F\u0440\u0438 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0438 \u0432 \u0411\u0430\u043D\u043A"] })) : (jsx(Paragraph, { size: "text-xl", font: "font-light", color: "text-primary-text", children: "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0434\u0440\u0443\u0433\u0443\u044E \u0441\u0443\u043C\u043C\u0443 \u0438\u043B\u0438 \u0441\u0440\u043E\u043A" })) })] })));
2062
-
2063
- const renderRate = ({ title = 'Ставка', rate = 0, unit = '%', fractionDigits = 2, isUnknownRateShown = true, ...rest }) => isUnknownRateShown && rate === 0 ? (jsx(UnknownRate, { title: title, ...rest })) : (jsxs("div", { children: [jsx("div", { className: "hidden @xl:block", children: jsx(Rate, { title: title, rate: rate, unit: unit, fractionDigits: fractionDigits, ...rest }) }), jsx("div", { className: "@xl:hidden", children: jsx(CalculatorValueBlock, { title: title, value: rate, postfix: unit, fractionDigits: fractionDigits, fixed: true, ...rest }) })] }));
2064
-
2065
- const DEFAULT_MONEY_STEP = 1000;
2066
- function renderWantedSumInput({ isShowItems = true, step = DEFAULT_MONEY_STEP, ...rest }) {
2067
- const items = [
2068
- `От ${toLocalNumberFormat(2)(rest.min || 0)} ₽`,
2069
- `До ${toLocalNumberFormat(2)(rest.max || 0)} ₽`,
2070
- ];
2071
- return jsx(InputRange, { items: isShowItems ? items : [], step: step, ...rest });
1995
+ const SENSITIVITY_SCALE = 10;
1996
+ function useIntersectionObserver(observerCallback, { sensitivity, rootRef } = {}) {
1997
+ const ref = useRef(null);
1998
+ const observerOptions = useMemo(() => ({ threshold: sensitivityToThreshold(sensitivity), root: rootRef?.current }), [sensitivity, rootRef?.current]);
1999
+ useEffect(() => {
2000
+ if (!ref.current) {
2001
+ return undefined;
2002
+ }
2003
+ const intersectionObserver = new IntersectionObserver(observerCallback, observerOptions);
2004
+ intersectionObserver.observe(ref.current);
2005
+ return () => {
2006
+ intersectionObserver.disconnect();
2007
+ };
2008
+ }, [observerCallback, observerOptions]);
2009
+ return ref;
2010
+ }
2011
+ function sensitivityToThreshold(sensitivity = 0) {
2012
+ const thresholdsCount = Math.round(sensitivity * SENSITIVITY_SCALE);
2013
+ return Array.from({ length: thresholdsCount })
2014
+ .fill(0)
2015
+ .map((_, thi) => Math.min(thi / thresholdsCount), 1)
2016
+ .concat(1);
2072
2017
  }
2073
2018
 
2074
- const BonusCalculatorForm = JSX(({ className = '', calcSource, buttons, footnote }) => {
2075
- const [userInputParams, { field }] = useForm({
2076
- travelExpenseValue: DEFAULT_TRAVEL_MIN_SUM,
2077
- restExpenseValue: DEFAULT_REST_MIN_SUM,
2078
- });
2079
- const { minSumTravel, maxSumTravel, minSumOther, maxSumOther, monthBonus, yearBonus } = getBonusCalculatorParams(userInputParams, calcSource);
2080
- return (jsxs("section", { className: style('flex flex-col justify-between @5xl:flex-row gap-y-4xl', className), children: [jsxs("div", { className: "flex flex-col @xl:max-w-md gap-2xl border-b border-b-main-divider @xl:border-none pb-3xl @xl:pb-0", children: [renderWantedSumInput({
2081
- title: 'Сумма покупок в категории путешествия, ₽',
2082
- min: minSumTravel,
2083
- max: maxSumTravel,
2084
- step: 500,
2085
- isShowItems: false,
2086
- ...field('travelExpenseValue'),
2087
- }), renderWantedSumInput({
2088
- title: 'Сумма покупок в остальных категориях, ₽',
2089
- min: minSumOther,
2090
- max: maxSumOther,
2091
- step: 500,
2092
- isShowItems: false,
2093
- ...field('restExpenseValue'),
2094
- })] }), monthBonus >= 0
2095
- ? renderRate({
2096
- title: 'Баллов за месяц',
2097
- rate: monthBonus,
2098
- fractionDigits: 0,
2099
- isUnknownRateShown: false,
2100
- unit: '',
2101
- })
2102
- : null, jsxs("div", { className: "flex flex-col gap-y-xl @xl:w-96", children: [yearBonus >= 0 ? jsx(CalculatorValueBlock, { title: "\u0411\u0430\u043B\u043B\u043E\u0432 \u0437\u0430 \u0433\u043E\u0434", value: yearBonus }) : null, renderButtonsSection(buttons, { isVertical: true }), renderFootnote(footnote)] })] }));
2019
+ const SwipeListItem = JSX(({ className, style: inlineStyle, idx, activeIndex, observerOptions, children, onIntersection, }) => {
2020
+ const observerCallback = useCallback((entries) => {
2021
+ if (entries.length && onIntersection) {
2022
+ onIntersection(idx || 0, entries[entries.length - 1]);
2023
+ }
2024
+ }, [onIntersection, idx]);
2025
+ const ref = useIntersectionObserver(observerCallback, observerOptions);
2026
+ return (jsx("div", { className: style('inline-block snap-always box-border', className), style: inlineStyle, "aria-current": Boolean(idx === activeIndex), ref: ref, role: "listitem", children: children }));
2103
2027
  });
2104
2028
 
2105
- const CalculatorFormLayout = JSX(({ className, inputChildren, rateChildren, resultChildren }) => (jsxs("section", { className: style('flex flex-col justify-between @5xl:flex-row gap-m', className), children: [jsx("div", { className: "flex flex-col gap-y-m sm:max-w-md gap-2xl", children: inputChildren }), jsx("div", { className: "border-b border-main-divider md:hidden" }), rateChildren, jsx("div", { className: "flex flex-col gap-2xl sm:w-96 max-w-full", children: resultChildren })] })));
2106
-
2107
- const getMoneyDefaultValue = (defaultParams, defaultMoney = 0) => defaultParams?.sum ? defaultParams.sum : defaultMoney;
2108
- const getMonthsDefaultValue = (defaultParams, defaultMonth = 0) => defaultParams?.period &&
2109
- defaultParams.period >= DEFAULT_MIN_MONTHS &&
2110
- defaultParams.period <= DEFAULT_MAX_MONTHS
2111
- ? defaultParams.period
2112
- : defaultMonth;
2113
-
2114
- const STEP_DAYS = 1;
2115
- const renderDaysInput = (props) => (jsx(InputRange, { items: [`От ${daysText(props.min || 0)}`, `До ${daysText(props.max || 0)}`], step: STEP_DAYS, ...props }));
2116
- const daysText = (days) => `${days} ${Number(days) % 10 === 1 ? 'дня' : 'дней'}`;
2029
+ const childStyleMap = ['min-w-full', '@lg:min-w-[50%]', '@lg:min-w-[33.3%]', '@lg:min-w-[25%]'];
2030
+ const SwipeListContainer = JSX(({ className, itemClassName, containerRef, activeIndex, visibleItemCount = 0, gap = 0, padding = 0, listType = 'horizontal-list', snapAlign = 'snap-center', minHeight, children, onVisibleItemsChange, onVisibleIndicesChange, }) => {
2031
+ const observerOptions = {
2032
+ sensitivity: 1,
2033
+ rootRef: containerRef,
2034
+ };
2035
+ const itemsVisibilityListRef = useRef([]);
2036
+ const handleIntersection = useCallback((idx, entry) => {
2037
+ const itemsVisibilityList = [...(itemsVisibilityListRef.current || [])];
2038
+ itemsVisibilityList[idx] = entry;
2039
+ itemsVisibilityListRef.current = itemsVisibilityList;
2040
+ onVisibleItemsChange && onVisibleItemsChange(itemsVisibilityList);
2041
+ onVisibleIndicesChange && onVisibleIndicesChange(getVisibleIndices(itemsVisibilityList));
2042
+ }, [onVisibleItemsChange, onVisibleIndicesChange]);
2043
+ const additionPadding = visibleItemCount > 0 ? gap * Number(visibleItemCount) - 1 : 0;
2044
+ const isVerticalList = listType === 'vertical-list';
2045
+ const isHorizontalList = listType === 'horizontal-list';
2046
+ const containerStyle = {
2047
+ ...(isHorizontalList ? getContainerOffset(padding, additionPadding) : {}),
2048
+ ...(isVerticalList ? getContainerHeight(minHeight) : {}),
2049
+ gap: `${gap}px`,
2050
+ };
2051
+ return (jsx("div", { className: style('box-border overflow-auto no-scrollbar', listType, className), style: containerStyle, ref: containerRef, role: "list", children: mapChildren((child, idx) => (jsx(SwipeListItem, { className: style(snapAlign, visibleItemCount ? childStyleMap[visibleItemCount - 1] : '', isVerticalList ? 'h-full' : '', itemClassName), style: isHorizontalList ? getContainerOffset(padding / 4) : {}, activeIndex: activeIndex, idx: idx, observerOptions: observerOptions, onIntersection: handleIntersection, children: child }, String(idx))))(children) }));
2052
+ });
2053
+ // Styles used to compensate outer container padding to get rid of clipping effect
2054
+ const getContainerOffset = (padding, additionalPadding = 0) => ({
2055
+ marginLeft: `${-padding}px`,
2056
+ marginRight: `${-padding}px`,
2057
+ paddingLeft: `${padding}px`,
2058
+ paddingRight: `${padding + additionalPadding}px`,
2059
+ });
2060
+ const getVisibleIndices = (itemsVisibilityList) => itemsVisibilityList
2061
+ .map((_, i) => [_, i])
2062
+ .filter(([_]) => _ && _.intersectionRatio >= 0.9)
2063
+ .map(([, i]) => i);
2117
2064
 
2118
- const orientationStyleMap = {
2119
- horizontal: 'gap-5xl',
2120
- vertical: 'flex-col gap-s',
2065
+ const containerStyles = {
2066
+ 'horizontal-list': 'mx-auto mt-lg w-fit',
2067
+ 'vertical-list': 'absolute flex-col justify-center h-full top-0 left-4',
2121
2068
  };
2122
- const RadioButtonGroup = JSX(({ items, checkedItem, className, onChange: onChangeCheckedItem, orientation = 'horizontal', }) => (jsx("div", { className: style(`flex ${orientationStyleMap[orientation]}`, className), role: "radiogroup", children: items?.map(({ id, text }) => (jsx(Checkbox, { isRadio: true, text: text, value: checkedItem === id, onChange: () => onChangeCheckedItem && onChangeCheckedItem(id) }, String(id)))) })));
2123
-
2124
- const renderBinaryRadioButtonGroup = ({ items, title, mobileTitle = title, orientation = 'horizontal', value, radioGroupClassName, onChange, }) => (jsxs("div", { className: "space-y-s", children: [title ? (jsx("div", { className: "hidden @5xl:block", children: jsx(Text, { color: "text-secondary-text", font: "font-light", children: title }) })) : null, mobileTitle ? (jsx("div", { className: "@5xl:hidden", children: jsx(Text, { color: "text-secondary-text", font: "font-light", children: mobileTitle }) })) : null, jsx(RadioButtonGroup, { orientation: orientation, items: items, checkedItem: items[value ? 0 : 1].id, className: style('flex-col sm:flex-row', radioGroupClassName), onChange: (_) => onChange && onChange(_ === items[0].id) })] }));
2125
-
2126
- const renderInterestPayment = (props) => renderBinaryRadioButtonGroup({
2127
- ...props,
2128
- items: [
2129
- { id: 'everyMonth', text: 'Ежемесячно' },
2130
- { id: 'atEndTerm', text: 'В конце срока' },
2131
- ],
2069
+ const activeDotStyles = {
2070
+ 'horizontal-list': 'w-6',
2071
+ 'vertical-list': 'h-6',
2072
+ };
2073
+ const dotStyle = 'cursor-pointer rounded-full transition-width duration-300 bg-primary-main group-data-secondary:bg-white group-data-transparent:bg-white';
2074
+ const inactiveDotStyle = 'w-2 h-2 opacity-30';
2075
+ const SwipeListControlDots = JSX(({ className = '', containerRef, activeIndex, dotCount = 0, listType = 'horizontal-list', version, }) => {
2076
+ const handleClick = (idx) => {
2077
+ if (idx !== activeIndex && containerRef?.current) {
2078
+ containerRef.current.children[idx]?.scrollIntoView({
2079
+ behavior: 'smooth',
2080
+ block: 'nearest',
2081
+ });
2082
+ }
2083
+ };
2084
+ return dotCount ? (jsx("div", { className: style('flex gap-xs', containerStyles[listType], version ? 'group' : '', className), "data-ver": version, children: Array.from({ length: dotCount })
2085
+ .fill(null)
2086
+ .map((_, idx) => (jsx("div", { className: `${dotStyle} ${idx === activeIndex ? activeDotStyles[listType] : inactiveDotStyle}`, role: "button", "aria-hidden": true, onClick: () => handleClick(idx) }, String(idx)))) })) : null;
2132
2087
  });
2133
2088
 
2089
+ const areArraysEqual = (as, bs) => as === bs || Boolean(as && bs && as?.length === bs?.length && as.every((_, i) => _ === bs[i]));
2090
+
2134
2091
  const defaultDiscriminator = (_) => _;
2135
2092
  const maxBy = (discriminator = defaultDiscriminator) => (list) => list.reduceRight((a, b) => (discriminator(a) > discriminator(b) ? a : b));
2136
2093
  const max = maxBy();
@@ -2138,906 +2095,1080 @@
2138
2095
  const minBy = (discriminator = defaultDiscriminator) => maxBy((_) => -discriminator(_));
2139
2096
  const min = minBy();
2140
2097
 
2141
- const getLimitRangeByKeys = ([minKey, maxKey], limitParams, rateRows = []) => limitParams && limitParams[minKey] && limitParams[maxKey]
2142
- ? {
2143
- [minKey]: limitParams[minKey],
2144
- [maxKey]: limitParams[maxKey],
2145
- }
2146
- : getRangeByKeys([minKey, maxKey], rateRows);
2147
- const getRangeByKeys = ([minKey, maxKey], rows = []) => ({
2148
- [minKey]: rows.length
2149
- ? min(rows.filter((row) => typeof row[minKey] === 'number').map((row) => row[minKey]))
2150
- : 0,
2151
- [maxKey]: rows.length
2152
- ? max(rows.filter((row) => typeof row[minKey] === 'number').map((row) => row[maxKey]))
2153
- : 0,
2154
- });
2155
- const checkRangeByKeys = (value, [minKey, maxKey], sourceBookRow) => value >= sourceBookRow[minKey] && value <= sourceBookRow[maxKey];
2156
-
2157
- const DEPOSIT_RATES_SERVICE_MAP = {
2158
- UL: 'businessdepositrates',
2159
- IP: 'selfemployeddepositrates',
2098
+ const getItemWidth = (container) => (itemCount) => container.scrollWidth / itemCount;
2099
+ const scrollBy = (container) => (itemCount, delta) => {
2100
+ container.scrollBy({ left: delta * getItemWidth(container)(itemCount), behavior: 'smooth' });
2160
2101
  };
2161
- function useBusinessDepositRates(rateParams) {
2162
- const serviceName = rateParams?.rateType ? DEPOSIT_RATES_SERVICE_MAP[rateParams?.rateType] : '';
2163
- const { data } = useAsyncData(serviceName ? `/api/v1/depositrates/${serviceName}` : null, fetchJSONUnsafe);
2164
- return Array.isArray(data) ? data : [];
2165
- }
2166
2102
 
2167
- const useBusinessDepositCalculatorParams = (userInputParams, rateParams) => {
2168
- const { daysValue, moneyValue, isPartialWithdrawal, isReplenished, isMonthlyInterestPayment } = userInputParams;
2169
- const isOperatingDeposit = isReplenished || isPartialWithdrawal;
2170
- const isDisabledOperatingDeposit = daysValue < BUSINESS_OPERATING_MIN_DAYS || daysValue > BUSINESS_OPERATING_MAX_DAYS;
2171
- const rateRows = useBusinessDepositRates(rateParams);
2172
- if (daysValue <= BUSINESS_RATE_MIN_DAYS) {
2173
- return {
2174
- ...DEFAULT_BUSINESS_DEPOSIT_CALCULATOR_PARAMS,
2175
- ...userInputParams,
2176
- isDisabledOperatingDeposit,
2177
- isUnavailableByTerm: true,
2178
- };
2179
- }
2180
- const businessDepositCalculatorSourceBookParams = rateRows?.find((_) => {
2181
- return (isMonthlyInterestPayment === _.isMonthlyInterestPayment &&
2182
- checkIsOperatingDeposit(isOperatingDeposit, _) &&
2183
- checkRangeByKeys(moneyValue, ['minSum', 'maxSum'], _) &&
2184
- checkRangeByKeys(daysValue, ['minDays', 'maxDays'], _));
2185
- }) || DEFAULT_BUSINESS_DEPOSIT_CALCULATOR_PARAMS;
2186
- const { rate } = businessDepositCalculatorSourceBookParams;
2187
- const income = getDepositIncome$1(rate, userInputParams);
2188
- const finalSum = moneyValue + income;
2189
- const sourceBookRows = rateRows || [DEFAULT_BUSINESS_DEPOSIT_CALCULATOR_PARAMS];
2190
- return {
2191
- ...businessDepositCalculatorSourceBookParams,
2192
- ...getRangeByKeys(['minSum', 'maxSum'], sourceBookRows),
2193
- ...getRangeByKeys(['minDays', 'maxDays'], sourceBookRows),
2194
- ...userInputParams,
2195
- isDisabledOperatingDeposit,
2196
- income,
2197
- finalSum,
2198
- };
2103
+ const scrollToEnd = (container) => {
2104
+ container.scrollTo({ left: container.scrollWidth, behavior: 'smooth' });
2199
2105
  };
2200
- const checkIsOperatingDeposit = (isOperatingDeposit, sourceBookRow) => isOperatingDeposit
2201
- ? sourceBookRow.isReplenished && sourceBookRow.isPartialWithdrawal
2202
- : !sourceBookRow.isReplenished && !sourceBookRow.isPartialWithdrawal;
2203
- const getDepositIncome$1 = (rate, userInputParams) => {
2204
- const { isMonthlyInterestPayment, moneyValue, daysValue } = userInputParams;
2205
- /*
2206
- Приблизительный расчёт, не учитывающий разницу в количестве дней в месяцах. Месяц принят за 30 дней, год за 365 дней.
2207
- */
2208
- if (isMonthlyInterestPayment) {
2209
- return Math.round(moneyValue * Math.pow((1200 + rate) / 1200, daysValue / 30) - moneyValue);
2210
- }
2211
- else {
2212
- return Math.round((moneyValue * rate * daysValue) / 365 / 100);
2213
- }
2106
+
2107
+ const scrollToStart = (container) => {
2108
+ container.scrollTo({ left: 0, behavior: 'smooth' });
2214
2109
  };
2215
2110
 
2216
- const BusinessDepositCalculatorForm = JSX(({ className = '', buttons, footnote, bottomFootnote, isShowDepositeName, defaultParams, rateParams, }) => {
2217
- const [userInputParams, { field }] = useForm({
2218
- moneyValue: getMoneyDefaultValue(defaultParams, BUSINESS_DEFAULT_SUM),
2219
- daysValue: getMonthsDefaultValue(defaultParams, BUSINESS_DEFAULT_DAYS),
2220
- isReplenished: Boolean(defaultParams?.isReplenished),
2221
- isPartialWithdrawal: Boolean(defaultParams?.isPartialWithdrawal),
2222
- isMonthlyInterestPayment: Boolean(defaultParams?.isMonthlyInterestPayment),
2223
- });
2224
- const calculatorParams = useBusinessDepositCalculatorParams(userInputParams, rateParams);
2225
- const { income, finalSum, rate, isUnavailableByTerm, depositeName } = calculatorParams;
2226
- return (jsx(CalculatorFormLayout, { className: className, inputChildren: jsxs("div", { children: [renderWantedSumInput({
2227
- title: 'Сумма, ₽',
2228
- min: calculatorParams.minSum,
2229
- max: calculatorParams.maxSum,
2230
- ...field('moneyValue'),
2231
- value: calculatorParams.moneyValue,
2232
- }), renderDaysInput({
2233
- title: 'Срок, дней',
2234
- min: calculatorParams.minDays,
2235
- max: calculatorParams.maxDays,
2236
- ...field('daysValue'),
2237
- value: calculatorParams.daysValue,
2238
- }), jsxs("div", { className: "flex flex-col sm:flex-row gap-y-4 gap-x-14", children: [renderBusinessDepositParamsForm(calculatorParams, field), renderInterestPayment({
2239
- title: 'Выплата процентов',
2240
- orientation: 'vertical',
2241
- ...field('isMonthlyInterestPayment'),
2242
- radioGroupClassName: 'sm:flex-col',
2243
- })] })] }), rateChildren: renderRate({
2244
- rate,
2245
- depositeName,
2246
- isShowDepositeName,
2247
- isUnavailableByTerm,
2248
- }), resultChildren: jsxs("div", { children: [renderDepositResults(finalSum, income), renderButtonsSection(buttons, { isVertical: true }), renderFootnote(footnote), renderFootnote(bottomFootnote)] }) }));
2249
- });
2250
- const renderBusinessDepositParamsForm = (calculatorParams, field) => (jsxs("div", { className: "space-y-s", children: [jsx(Text, { color: "text-secondary-text", font: "font-light", children: "\u041F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B" }), jsxs("div", { className: "flex flex-col gap-y-xl sm:gap-y-s", children: [jsx(Checkbox, { text: "\u041F\u043E\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435", disabled: calculatorParams.isDisabledOperatingDeposit, ...field('isReplenished'), value: calculatorParams.isReplenished }), jsx(Checkbox, { disabled: calculatorParams.isDisabledOperatingDeposit, text: "\u0427\u0430\u0441\u0442\u0438\u0447\u043D\u043E\u0435 \u0441\u043D\u044F\u0442\u0438\u0435", ...field('isPartialWithdrawal'), value: calculatorParams.isPartialWithdrawal })] })] }));
2251
- const renderDepositResults = (finalSum, income) => (jsxs("div", { className: "flex flex-col gap-2xl sm:gap-0 mb-lg", children: [jsx(CalculatorValueBlock, { title: "\u0414\u043E\u0445\u043E\u0434 \u0432 \u043A\u043E\u043D\u0446\u0435 \u0441\u0440\u043E\u043A\u0430", value: income, postfix: "\u20BD", prefix: "+" }), jsx(CalculatorValueBlock, { title: "\u0421\u0443\u043C\u043C\u0430 \u0432 \u043A\u043E\u043D\u0446\u0435 \u0441\u0440\u043E\u043A\u0430", value: finalSum, postfix: "\u20BD" })] }));
2252
-
2253
- const checkCreditSourceBookRowCommonParams = (props) => checkRowParam$1({
2254
- ...props,
2255
- fieldKey: 'isSalaryClient',
2256
- viewSettingFieldKey: 'isShowSalaryClient',
2257
- }) &&
2258
- checkRowParam$1({
2259
- ...props,
2260
- fieldKey: 'isStateEmployee',
2261
- viewSettingFieldKey: 'isShowStateEmployee',
2262
- }) &&
2263
- checkRowParam$1({
2264
- ...props,
2265
- fieldKey: 'isPensionClient',
2266
- viewSettingFieldKey: 'isShowPensionClient',
2267
- }) &&
2268
- checkRowParam$1({
2269
- ...props,
2270
- fieldKey: 'isInsurance',
2271
- viewSettingFieldKey: 'isShowInsurance',
2272
- });
2273
- const checkRowParam$1 = ({ row, userInputParams, paramsViewSettings, fieldKey, viewSettingFieldKey, }) => {
2274
- const isExclude = paramsViewSettings ? !paramsViewSettings[viewSettingFieldKey] : false;
2275
- return isExclude || userInputParams[fieldKey] === row[fieldKey];
2111
+ const useSwipeListScroll = ({ itemCount, onVisibleIndicesChange, }) => {
2112
+ const containerRef = useRef(null);
2113
+ const [visibleIndices, setVisibleIndices] = useState([]);
2114
+ const handleVisibleIndicesChange = useCallback((indices) => {
2115
+ setVisibleIndices((prevIndices) => areArraysEqual(prevIndices, indices) ? prevIndices : indices);
2116
+ onVisibleIndicesChange && onVisibleIndicesChange(indices);
2117
+ }, [itemCount]);
2118
+ const [minIndex, maxIndex] = visibleIndices.length
2119
+ ? [minBy()(visibleIndices), maxBy()(visibleIndices)]
2120
+ : [-1, -1];
2121
+ const canScrollLeft = minIndex > 0;
2122
+ const canScrollRight = maxIndex < itemCount - 1;
2123
+ const scrollLeft = useCallback(() => {
2124
+ if (!containerRef.current) {
2125
+ return;
2126
+ }
2127
+ if (canScrollLeft) {
2128
+ scrollBy(containerRef.current)(itemCount, -1);
2129
+ }
2130
+ else {
2131
+ scrollToEnd(containerRef.current);
2132
+ }
2133
+ }, [itemCount, canScrollLeft]);
2134
+ const scrollRight = useCallback(() => {
2135
+ if (!containerRef.current) {
2136
+ return;
2137
+ }
2138
+ if (canScrollRight) {
2139
+ scrollBy(containerRef.current)(itemCount, 1);
2140
+ }
2141
+ else {
2142
+ scrollToStart(containerRef.current);
2143
+ }
2144
+ }, [itemCount, canScrollRight]);
2145
+ const scrollTo = useCallback((idx, options = {
2146
+ behavior: 'smooth',
2147
+ block: 'nearest',
2148
+ inline: 'center',
2149
+ }) => {
2150
+ if (containerRef?.current) {
2151
+ containerRef.current.children[idx]?.scrollIntoView(options);
2152
+ }
2153
+ }, [containerRef]);
2154
+ return [
2155
+ containerRef,
2156
+ {
2157
+ scrollLeft,
2158
+ scrollRight,
2159
+ scrollTo,
2160
+ canScrollLeft,
2161
+ canScrollRight,
2162
+ visibleIndicesRange: [minIndex, maxIndex],
2163
+ handleVisibleIndicesChange,
2164
+ },
2165
+ ];
2276
2166
  };
2277
2167
 
2278
- const getCreditCalculatorLimitParams = ({ limitRows, userInputParams, paramsViewSettings, }) => limitRows?.find((row) => checkCreditSourceBookRowCommonParams({
2279
- row,
2280
- userInputParams,
2281
- paramsViewSettings,
2282
- }));
2283
-
2284
- const getCreditCalculatorRateParams = ({ rateRows = [], userInputParams, paramsViewSettings, moneyValue, monthsValue, }) => rateRows?.find((row) => checkCreditSourceBookRowCommonParams({
2285
- row,
2286
- userInputParams,
2287
- paramsViewSettings,
2288
- }) &&
2289
- checkRangeByKeys(moneyValue, ['minSum', 'maxSum'], row) &&
2290
- checkRangeByKeys(monthsValue, ['minMonths', 'maxMonths'], row)) || DEFAULT_CREDIT_CALCULATOR_PARAMS;
2168
+ /** @deprecated */
2169
+ const ArrowButton = JSX(({ className, disabled, ...rest }) => (jsx(LinkButton, { rounded: true, className: style('group/btn w-12 h-12 min-h-12 max-h-12 flex items-center justify-center shadow-main-black', disabled
2170
+ ? 'bg-secondary-light text-secondary-text'
2171
+ : 'bg-white text-primary-text hover:text-primary-main', className || ''), disabled: disabled, ...rest, children: jsx(Img, { className: "w-4 h-4 min-w-4 min-h-4 rounded-full box-border", image: { icon: 'ArrowLeftIcon', iconVersion: 'black' }, imageClassName: "group-hover/btn:text-primary-main", width: "16", height: "16" }) })));
2172
+ /** @deprecated */
2173
+ const ScrollLeftButton = JSX((props) => (jsx(ArrowButton, { ...props, ariaLabel: "\u041F\u0440\u043E\u043B\u0438\u0441\u0442\u0430\u0442\u044C \u0432\u043B\u0435\u0432\u043E", "data-block-control": "scroll-left" })));
2174
+ /** @deprecated */
2175
+ const ScrollRightButton = JSX((props) => (jsx(ArrowButton, { ...props, className: `${props.className || ''} rotate-180`, ariaLabel: "\u041F\u0440\u043E\u043B\u0438\u0441\u0442\u0430\u0442\u044C \u0432\u043F\u0440\u0430\u0432\u043E", "data-block-control": "scroll-right" })));
2291
2176
 
2292
- /**
2293
- * Формулы расчёта аннуитетных и дифференцированных платежей:
2294
- * https://finuslugi.ru/potrebitelskie_kredity/stat_annuitetnye_i_differentsirovannye_platezhi
2295
- */
2296
- const getMonthlyPayment = (rate, params) => {
2297
- const { isAnnuity, moneyValue, monthsValue, moneyExtra } = params;
2298
- if (rate === 0) {
2299
- return 0;
2300
- }
2301
- else if (isAnnuity) {
2302
- const i = rate / MONTHLY_INTEREST_RATE; // Используется значение ставки в процентах
2303
- if (moneyExtra) {
2304
- return (moneyValue + moneyExtra) * (i + i / (Math.pow(1 + i, monthsValue) - 1));
2177
+ const scrollButtonStyle$1 = 'absolute top-[calc(50%-24px)] z-40';
2178
+ const blurBaseStyle = 'absolute top-0 bottom-0 z-40 hidden @lg:block w-20 pointer-events-none';
2179
+ const CarouselScrollButtons = JSX(({ scroll, activeIndex, itemCount, arrowsPadded, hasEdgeBlocks, onScrollLeft, onScrollRight, }) => {
2180
+ const { canScrollLeft, canScrollRight, scrollLeft, scrollRight } = scroll;
2181
+ const canMoveLeft = hasEdgeBlocks ? activeIndex > 0 : canScrollLeft;
2182
+ const canMoveRight = hasEdgeBlocks ? activeIndex < itemCount - 1 : canScrollRight;
2183
+ const handleScrollLeft = useCallback(() => {
2184
+ if (!hasEdgeBlocks) {
2185
+ scrollLeft();
2305
2186
  }
2306
2187
  else {
2307
- return moneyValue * (i + i / (Math.pow(1 + i, monthsValue) - 1));
2188
+ canScrollLeft && activeIndex !== itemCount - 1 && scrollLeft();
2189
+ onScrollLeft();
2308
2190
  }
2309
- }
2310
- else {
2311
- return Math.round(moneyValue / monthsValue) + moneyValue * (rate / (MONTHS_IN_YEAR * 100)); // Используется значение ставки (rate) в процентах, делённое на сто.
2312
- }
2313
- };
2191
+ }, [scrollLeft, onScrollLeft, hasEdgeBlocks, canScrollLeft, activeIndex, itemCount]);
2192
+ const handleScrollRight = useCallback(() => {
2193
+ if (!hasEdgeBlocks) {
2194
+ scrollRight();
2195
+ }
2196
+ else {
2197
+ canScrollRight && activeIndex !== 0 && scrollRight();
2198
+ onScrollRight();
2199
+ }
2200
+ }, [scrollRight, onScrollRight, hasEdgeBlocks, canScrollLeft, activeIndex, itemCount]);
2201
+ return (jsxs("div", { className: "m:hidden block", children: [renderBlur(scroll), canMoveLeft ? (jsx(ScrollLeftButton, { className: style(scrollButtonStyle$1, arrowsPadded ? 'left-1 m:left-8' : 'left-0'), onClick: handleScrollLeft })) : null, canMoveRight ? (jsx(ScrollRightButton, { className: style(scrollButtonStyle$1, arrowsPadded ? 'right-1 m:right-8' : 'right-0'), onClick: handleScrollRight })) : null] }));
2202
+ });
2203
+ const renderBlur = (scroll) => (jsxs("div", { children: [scroll.canScrollLeft ? (jsx("div", { className: style(blurBaseStyle, 'left-0 bg-opacity-from-white group-data-secondary:bg-none') })) : null, scroll.canScrollRight ? (jsx("div", { className: style(blurBaseStyle, 'right-0 bg-opacity-to-white group-data-secondary:bg-none') })) : null] }));
2314
2204
 
2315
- const getCreditCalculatorParams = (userInputParams, calcSource, paramsViewSettings) => {
2316
- const { moneyValue, monthsValue } = userInputParams;
2317
- const rateRows = calcSource?.rateRows || [DEFAULT_CREDIT_CALCULATOR_PARAMS];
2318
- const limitRows = calcSource?.limitRows || [];
2319
- const creditCalculatorSourceBookLimitParams = getCreditCalculatorLimitParams({
2320
- limitRows,
2321
- userInputParams,
2322
- paramsViewSettings,
2323
- });
2324
- const { minSum, maxSum } = getLimitRangeByKeys(['minSum', 'maxSum'], creditCalculatorSourceBookLimitParams, rateRows);
2325
- const { minMonths, maxMonths } = getLimitRangeByKeys(['minMonths', 'maxMonths'], creditCalculatorSourceBookLimitParams, rateRows);
2326
- const creditCalculatorRateParams = getCreditCalculatorRateParams({
2327
- rateRows,
2328
- userInputParams,
2329
- paramsViewSettings,
2330
- moneyValue,
2331
- monthsValue,
2205
+ const ITEMS_GAP = 16;
2206
+
2207
+ const renderCardHighlight = ({ className, text = 'ХИТ' } = {}) => (jsxs("div", { className: style('w-14 h-14 bg-white flex flex-col items-center justify-center rounded-md shadow-main-black', className), children: [jsx(Icon, { iconVersion: "black", name: "HeartIcon", width: "24", height: "24" }), jsx(Paragraph, { size: "text-s", font: "font-normal", align: "text-center", color: "text-primary-text", children: text })] }));
2208
+
2209
+ const cardBaseStyle = 'relative h-full';
2210
+ const CARD_SIZE_MAP = {
2211
+ small: 'sm:min-w-64',
2212
+ normal: 'sm:min-w-96',
2213
+ big: 'sm:min-w-[515px]',
2214
+ };
2215
+ const renderCarouselCards = ({ noCardBorder, activeIndex, cardSize = 'normal', ...rest }) => {
2216
+ const { blocks: childBlocks } = rest.block || {};
2217
+ const someCardsHighlighted = childBlocks?.some(isCardHighlighted);
2218
+ return renderChildren({
2219
+ ...rest,
2220
+ renderProps: (block, options, blockIndex) => (jsxs("div", { className: cardWrapperStyle(block, { someCardsHighlighted }), children: [renderBlock$1(block, options, {
2221
+ className: cardStyle(block, {
2222
+ someCardsHighlighted,
2223
+ showBorder: !noCardBorder,
2224
+ cardSize,
2225
+ }),
2226
+ blockIndex,
2227
+ isActive: blockIndex === activeIndex,
2228
+ }), isCardHighlighted(block)
2229
+ ? renderCardHighlight({ className: 'absolute top-0 -right-4 z-40' })
2230
+ : null] }, String(blockIndex))),
2332
2231
  });
2333
- const { rate } = creditCalculatorRateParams;
2334
- const monthlyPayment = getMonthlyPayment(rate, userInputParams);
2335
- return {
2336
- ...creditCalculatorRateParams,
2337
- ...userInputParams,
2338
- minSum,
2339
- maxSum,
2340
- minMonths,
2341
- maxMonths,
2342
- moneyValue,
2343
- monthsValue,
2344
- monthlyPayment,
2345
- };
2346
2232
  };
2233
+ const cardWrapperStyle = (_, { someCardsHighlighted }) => style(cardBaseStyle, someCardsHighlighted && (isCardHighlighted(_) ? 'pt-3xl' : 'pt-3xl sm:pt-16'));
2234
+ const cardStyle = (_, { someCardsHighlighted, showBorder, cardSize = 'normal', }) => style(cardBaseStyle, 'min-w-80', CARD_SIZE_MAP[cardSize], // TODO fix для RecommendationCard
2235
+ someCardsHighlighted && (isCardHighlighted(_) ? 'pt-3xl sm:pt-16' : 'pt-3xl'), getBorderStyle(showBorder), showBorder && 'h-full');
2236
+ const isCardHighlighted = (_) => _?.content && 'isHighlighting' in _.content && _.content.isHighlighting;
2347
2237
 
2348
- const STEP_MONTHS = 1;
2349
- const renderMonthsInput = ({ min = 0, max = 0, availableMonths, ...rest }) => (jsx(InputRange, { items: [`От ${monthText(min)}`, `До ${monthText(max)}`], step: STEP_MONTHS, ...(availableMonths?.length ? { list: availableMonths } : { min, max }), ...rest }));
2350
- const monthText = (month) => `${month} ${Number(month) % 10 === 1 ? 'месяца' : 'месяцев'}`;
2351
-
2352
- const renderPaymentType = (props) => renderBinaryRadioButtonGroup({
2353
- ...props,
2354
- radioGroupClassName: 'sm:flex-col',
2355
- items: [
2356
- { id: 'annuity', text: 'Аннуитетный' },
2357
- { id: 'differential', text: 'Дифференцированный' },
2358
- ],
2359
- });
2238
+ const VISIBLE_ITEM_COUNT = 1;
2360
2239
 
2361
- const CreditCalculatorForm = JSX(({ className, calcSource, buttons, footnote, paramsViewSettings, defaultParams }) => {
2362
- const [userInputParams, { field }] = useForm({
2363
- moneyValue: getMoneyDefaultValue(defaultParams, CREDIT_DEFAULT_SUM),
2364
- monthsValue: getMonthsDefaultValue(defaultParams, DEFAULT_MONTHS),
2365
- isAnnuity: Boolean(defaultParams?.isAnnuity),
2366
- isInsurance: Boolean(defaultParams?.isEnableInsurance),
2367
- isSalaryClient: Boolean(defaultParams?.isEnableSalaryClient),
2368
- isPensionClient: Boolean(defaultParams?.isEnablePensionClient),
2369
- isStateEmployee: Boolean(defaultParams?.isEnableStateEmployee),
2370
- });
2371
- const calculatorParams = getCreditCalculatorParams(userInputParams, calcSource, paramsViewSettings);
2372
- const paymentTypeElement = renderPaymentType({
2373
- title: '',
2374
- mobileTitle: 'Тип платежа',
2375
- orientation: 'vertical',
2376
- ...field('isAnnuity'),
2240
+ const Carousel = UniBlock(({ className, itemCount, visibleItemCount = VISIBLE_ITEM_COUNT, noCardBorder, isAlignCenter, arrowsPadded = true, isGap = true, isEmbedded = false, cardSize = 'normal', ...rest }) => {
2241
+ const { blocks: childBlocks = [] } = rest.block || {};
2242
+ const count = itemCount || childBlocks?.length;
2243
+ const [activeIndex, { inc: increaseActiveIndex, dec: decreaseActiveIndex }] = useCarouselControls({
2244
+ itemCount: count,
2245
+ visibleItemCount,
2246
+ }, Math.floor(visibleItemCount / 2));
2247
+ const [containerRef, scroll] = useSwipeListScroll({ itemCount: count });
2248
+ const [dotsActiveIndex] = scroll.visibleIndicesRange;
2249
+ // TODO Remove
2250
+ const isBankHistoryCarousel = childBlocks && childBlocks[0]?.type === 'CarouselBankHistoryCard';
2251
+ return (jsxs(BlockWrapper, { className: style(isEmbedded ? '@container-normal' : '', className), defaultPadding: "p-6xl", ...rest, children: [jsxs("div", { className: style(isAlignCenter ? 'flex justify-center' : '', 'relative max-w-full'), children: [jsx(SwipeListContainer, { containerRef: containerRef, visibleItemCount: visibleItemCount, snapAlign: "snap-start", gap: isGap ? ITEMS_GAP : undefined, onVisibleIndicesChange: scroll.handleVisibleIndicesChange, children: renderCarouselCards({ noCardBorder, activeIndex, cardSize, ...rest }) }), jsx(CarouselScrollButtons, { scroll: scroll, arrowsPadded: arrowsPadded, onScrollLeft: decreaseActiveIndex, onScrollRight: increaseActiveIndex, hasEdgeBlocks: isBankHistoryCarousel, activeIndex: activeIndex, itemCount: count })] }), jsx(SwipeListControlDots, { className: "@5xl:hidden", containerRef: containerRef, activeIndex: dotsActiveIndex, dotCount: count, listType: "horizontal-list" })] }));
2252
+ }, {
2253
+ childrenTypes: [
2254
+ 'CarouselCard',
2255
+ 'CarouselBankHistoryCard',
2256
+ 'CarouselCatalogCard',
2257
+ 'CarouselRecommendationCard',
2258
+ 'CarouselTariffsCard',
2259
+ 'RichTextBlock',
2260
+ 'ProductBlock',
2261
+ ],
2262
+ childSchema: () => ({
2263
+ isHighlighting: {
2264
+ title: 'Выделить карточку',
2265
+ type: 'boolean',
2266
+ },
2267
+ }),
2268
+ });
2269
+
2270
+ const EMPTY_SCHEMA = {};
2271
+ const initialFiltrationState$3 = {};
2272
+ const BankAccountFiltration = UniBlock(({ className, filtrationSchema = EMPTY_SCHEMA, resetButton, notFound, ...rest }) => {
2273
+ const [, { blocksToRender }, { field, reset }] = useFiltrationForm(initialFiltrationState$3, {
2274
+ block: rest.block,
2275
+ page: rest.options?.page,
2377
2276
  });
2378
- const { onChange: setMoneyValue } = field('moneyValue');
2379
- const { onChange: setMonthsValue } = field('monthsValue');
2380
- useEffect(() => {
2381
- setMoneyValue &&
2382
- setMoneyValue(clamp(userInputParams.moneyValue, calculatorParams.minSum, calculatorParams.maxSum));
2383
- }, [calculatorParams.minSum, calculatorParams.maxSum]);
2384
- useEffect(() => {
2385
- setMonthsValue &&
2386
- setMonthsValue(clamp(userInputParams.monthsValue, calculatorParams.minMonths, calculatorParams.maxMonths));
2387
- }, [calculatorParams.minMonths, calculatorParams.maxMonths]);
2388
- return (jsx(CalculatorFormLayout, { className: className, inputChildren: jsxs("div", { children: [renderWantedSumInput({
2389
- title: 'Сумма кредита, ₽',
2390
- step: 500,
2391
- min: calculatorParams.minSum,
2392
- max: calculatorParams.maxSum,
2393
- ...field('moneyValue'),
2394
- value: calculatorParams.moneyValue,
2395
- }), renderMonthsInput({
2396
- title: 'Срок кредита, месяцев',
2397
- min: calculatorParams.minMonths,
2398
- max: calculatorParams.maxMonths,
2399
- ...field('monthsValue'),
2400
- value: calculatorParams.monthsValue,
2401
- }), renderCreditParamsForm$1({ ...calculatorParams, ...paramsViewSettings }, field), jsx("div", { className: "sm:hidden", children: paymentTypeElement })] }), rateChildren: renderRate({
2402
- rate: calculatorParams.rate,
2403
- }), resultChildren: jsxs("div", { children: [jsx(CalculatorValueBlock, { title: "\u0415\u0436\u0435\u043C\u0435\u0441\u044F\u0447\u043D\u044B\u0439 \u043F\u043B\u0430\u0442\u0451\u0436", value: calculatorParams.monthlyPayment, postfix: "\u20BD" }), jsx("div", { className: "hidden sm:block mb-lg", children: paymentTypeElement }), renderButtonsSection(buttons, { isVertical: true }), renderFootnote(footnote)] }) }));
2277
+ const selectArrProps = Object.entries(filtrationSchema)
2278
+ // Only StringArray
2279
+ .filter(([, schema]) => schema.type === 'array' && schema.ui === 'dropdown')
2280
+ .map(([name, schema]) => ({
2281
+ className: 'w-64',
2282
+ label: schema.title,
2283
+ options: stringArrayToOptions(schema),
2284
+ ...field(name, {
2285
+ parse: ({ key }) => [key],
2286
+ format: ([value] = []) => stringArrayToOptions(schema).find((_) => _.key === value) || { key: '' },
2287
+ }),
2288
+ }));
2289
+ const Selects = selectArrProps.map((props, i) => jsx(Select, { ...props }, String(i)));
2290
+ return (jsxs(BlockWrapper, { className: style(className), defaultPadding: "p-0", version: "transparent", ...rest, children: [jsx(FiltrationForm, { filtrationSchema: filtrationSchema, field: field, reset: reset, children: jsxs("div", { children: [Selects, jsx("div", { children: jsx(Button, { type: "reset", className: "mt-lg w-64", onClick: reset, version: resetButton?.version, children: resetButton?.text }) })] }) }), jsx("div", { className: style(blocksToRender?.length ? 'hidden' : 'block'), children: notFound ? jsx(NotFound, { ...notFound }) : null }), jsx(Carousel, { padding: "p-0", isEmbedded: true, itemCount: blocksToRender?.length, visibleItemCount: 0, isAlignCenter: true, isGap: false, children: renderBlocksList(blocksToRender, {
2291
+ ...rest.options,
2292
+ parent: rest.block,
2293
+ renderProps: (block, options, i) => (jsx("div", { className: style('relative min-w-80', getBorderStyle(true)), children: renderBlock$1(block, options) }, String(i))),
2294
+ }) })] }));
2295
+ }, {
2296
+ childrenTypes: ['CarouselTariffsCard'],
2297
+ childSchema: (content) => content?.filtrationSchema || {},
2404
2298
  });
2405
- const renderCreditParamsForm$1 = (params, field) => (jsxs("div", { className: "flex flex-col gap-y-2xl", children: [params.isShowSalaryClient ? (jsx(Checkbox, { text: `Получаю зарплату на счёт\nв Россельхозбанке`, ...field('isSalaryClient'), value: params.isSalaryClient, className: "whitespace-pre-line sm:whitespace-normal" })) : null, params.isShowStateEmployee ? (jsx(Checkbox, { text: "\u0420\u0430\u0431\u043E\u0442\u0430\u044E \u0432 \u0431\u044E\u0434\u0436\u0435\u0442\u043D\u043E\u0439 \u043E\u0440\u0433\u0430\u043D\u0438\u0437\u0430\u0446\u0438\u0438", ...field('isStateEmployee'), value: params.isStateEmployee })) : null, params.isShowPensionClient ? (jsx(Checkbox, { text: "\u041F\u043E\u043B\u0443\u0447\u0430\u044E \u043F\u0435\u043D\u0441\u0438\u044E \u043D\u0430 \u043A\u0430\u0440\u0442\u0443 \u0420\u043E\u0441\u0441\u0435\u043B\u044C\u0445\u043E\u0437\u0431\u0430\u043D\u043A\u0430", ...field('isPensionClient'), value: params.isPensionClient })) : null, params.isShowInsurance ? (jsx(Checkbox, { text: "\u041A\u043E\u043C\u043F\u043B\u0435\u043A\u0441\u043D\u0430\u044F \u0441\u0442\u0440\u0430\u0445\u043E\u0432\u0430\u044F \u0437\u0430\u0449\u0438\u0442\u0430", ...field('isInsurance'), value: params.isInsurance })) : null] }));
2299
+ const stringArrayToOptions = (stringEnum) => zip(stringEnum.items?.enum || [], stringEnum.items?.enumNames || []).map(([key, text]) => ({
2300
+ key,
2301
+ text,
2302
+ }));
2406
2303
 
2407
- const arrayByRange = (start, stop, step = 1) => Array.from({ length: Math.abs(stop - start) / step + 1 }, (value, index) => stop >= start ? start + index * step : start - index * step);
2304
+ const iconBgStyles = {
2305
+ '': 'bg-main-divider group-data-secondary:bg-white/30',
2306
+ primary: 'bg-main-divider',
2307
+ secondary: 'bg-secondary-light',
2308
+ opacity: 'bg-white/30',
2309
+ };
2310
+ const RoundedIcon = JSX(({ className, defaultIconSize = 44, iconBgVersion = 'primary', size, icon, ...rest }) => {
2311
+ const widthAndHeight = String(size?.width || defaultIconSize);
2312
+ return icon ? (jsx("div", { style: {
2313
+ width: `${widthAndHeight}px`,
2314
+ height: `${widthAndHeight}px`,
2315
+ }, className: style('rounded-icon object-fit flex justify-center items-center', iconBgStyles[iconBgVersion], className), children: jsx(Icon, { ...rest, width: "54%", height: "54%", name: icon }) })) : null;
2316
+ });
2408
2317
 
2409
- const mergeIntervals = (intervals) => {
2410
- const result = [...intervals];
2411
- result.sort(([a], [c]) => a - c);
2412
- let i = 1;
2413
- while (i < result.length) {
2414
- const [a, b] = result[i - 1];
2415
- const [c, d] = result[i];
2416
- if (b >= c) {
2417
- result.splice(i - 1, 2, [a, Math.max(b, d)]);
2418
- }
2419
- else {
2420
- i++;
2421
- }
2318
+ const defaultIconSize = 44;
2319
+
2320
+ const BenefitsBlock = JSX(({ className = '', title, benefitList, ...rest }) => (jsxs(BlockWrapper, { className: className, defaultPadding: "p-6xl", ...rest, children: [title ? jsx(Heading, { headingType: "h3", as: "h2", className: AlignStyles, title: title }) : null, benefitList?.length ? (jsx("div", { className: "flex flex-col @md:flex-row flex-wrap w-full @md:justify-center mt-xl gap-5xl", role: "list", children: benefitList.map(renderBenefit) })) : null] })));
2321
+ const renderBenefit = ({ label, description, items, icon, iconBgVersion, isDotted = false }, i) => (jsxs("div", { className: "flex gap-m items-center @md:basis-1/2 @md:max-w-[500px]", role: "listitem", children: [icon?.icon ? (jsx(RoundedIcon, { className: "shrink-0", defaultIconSize: defaultIconSize, iconBgVersion: iconBgVersion || '', ...icon })) : null, icon?.src ? jsx(Img, { className: "shrink-0", image: icon }) : null, label ? (jsxs("div", { className: "@sm:h-full space-y-xs", children: [jsx(Heading, { headingType: "h6", children: label }), jsx(Text, { size: "text-l", color: "text-secondary-text group-data-secondary:text-white/80", font: "font-light", children: description }), jsx(List, { dotClassName: "bg-secondary-text", listItemSize: "S", items: items, isDotted: isDotted })] })) : null] }, String(i)));
2322
+
2323
+ const bonusBenefitStyles = 'items-center';
2324
+
2325
+ const renderBonusBenefit = (cardTextAlign) => ({ icon, title, description }, i) => (jsxs("div", { className: style('flex flex-col gap-1', bonusBenefitStyles, !description && !title ? 'justify-center' : ''), children: [title ? (jsx(Text, { size: "text-h6", font: "font-normal", align: cardTextAlign, children: title })) : null, icon ? jsx(Img, { image: icon, height: "180", className: "w-auto mx-auto" }) : null, description ? (jsx(Text, { size: "text-h6", font: "font-normal", align: cardTextAlign, children: description })) : null] }, String(i)));
2326
+
2327
+ const renderBonusBenefits = (bonusBenefits, columnsCount, cardTextAlign) => (jsx("div", { className: `grid gap-2xs ${getElementsColsValue(columnsCount)}`, children: bonusBenefits.map(renderBonusBenefit(cardTextAlign)) }));
2328
+ const getElementsColsValue = (colValue) => {
2329
+ switch (colValue) {
2330
+ case '3':
2331
+ return 'grid-cols-3';
2332
+ case '4':
2333
+ return 'grid-cols-4';
2334
+ case '5':
2335
+ return 'grid-cols-5';
2336
+ default:
2337
+ return 'grid-cols-5';
2422
2338
  }
2423
- return result;
2424
2339
  };
2425
2340
 
2426
- const getDepositCalculatorParams = (userInputParams, calcSource, paramsViewSettings) => {
2427
- const { moneyValue, monthsValue } = userInputParams;
2428
- const defaultMonth = calcSource?.rateRows?.[0]
2429
- ? getMiddleMonth(calcSource.rateRows[0].minMonths, calcSource.rateRows[0].maxMonths)
2430
- : 0;
2431
- const isExcludeMonthlyInterestPayment = !paramsViewSettings?.isShowMonthlyInterestPayment;
2432
- const isExcludeRemoteRegistration = !paramsViewSettings?.isShowRemoteRegistration;
2433
- const depositCalculatorSourceBookParams = calcSource?.rateRows?.find((row) => checkRowByParam({
2434
- row,
2435
- userInputParams,
2436
- paramKey: 'isMonthlyInterestPayment',
2437
- isExclude: isExcludeMonthlyInterestPayment,
2438
- }) &&
2439
- checkRowByParam({
2440
- row,
2441
- userInputParams,
2442
- paramKey: 'isRemoteRegistration',
2443
- isExclude: isExcludeRemoteRegistration,
2444
- }) &&
2445
- checkRangeByKeys(moneyValue, ['minSum', 'maxSum'], row) &&
2446
- checkRangeByKeys(monthsValue || defaultMonth, ['minMonths', 'maxMonths'], row)) || DEFAULT_DEPOSIT_CALCULATOR_PARAMS;
2447
- const { rate } = depositCalculatorSourceBookParams;
2448
- const income = getDepositIncome(rate, userInputParams);
2449
- const finalSum = moneyValue + income;
2450
- const sourceBookRows = calcSource?.rateRows || [DEFAULT_DEPOSIT_CALCULATOR_PARAMS];
2451
- return {
2452
- ...depositCalculatorSourceBookParams,
2453
- ...getRangeByKeys(['minSum', 'maxSum'], sourceBookRows),
2454
- ...getRangeByKeys(['minMonths', 'maxMonths'], sourceBookRows),
2455
- availableMonths: mergeIntervals(sourceBookRows.map((row) => [row['minMonths'], row['maxMonths']])).flatMap(([start, end]) => arrayByRange(start, end)),
2456
- ...userInputParams,
2457
- defaultMonth,
2458
- income,
2459
- finalSum,
2460
- };
2461
- };
2462
- const getDepositIncome = (rate, userInputParams) => {
2463
- const { isMonthlyInterestPayment, moneyValue, monthsValue } = userInputParams;
2464
- if (isMonthlyInterestPayment) {
2465
- return Math.round(moneyValue * Math.pow((1200 + rate) / 1200, monthsValue) - moneyValue);
2466
- }
2467
- else {
2468
- /*
2469
- Приблизительный расчёт, не учитывающий разницу в количестве дней в месяцах. Месяц принят за 30 дней, год за 365 дней.
2470
- */
2471
- return Math.round((moneyValue * rate * 30 * monthsValue) / 365 / 100);
2472
- }
2341
+ const BonusBenefitsBlock = JSX(({ bonusBenefits, cardTextAlign = 'text-center', className = '', columnsCount = '4', subtitle, title, ...rest }) => (jsxs(BlockWrapper, { className: style('space-y-3xl', className), defaultPadding: "p-6xl", ...rest, children: [jsx(Headline, { defaultPadding: "", title: title, description: subtitle, headlineVersion: "M" }), bonusBenefits?.length
2342
+ ? renderBonusBenefits(bonusBenefits, columnsCount, cardTextAlign)
2343
+ : null] })));
2344
+
2345
+ const AlignJustifyStyle = {
2346
+ start: 'justify-start',
2347
+ center: 'justify-center',
2348
+ end: 'justify-end',
2473
2349
  };
2474
- const checkRowByParam = ({ row, userInputParams, paramKey, isExclude }) => isExclude || userInputParams[paramKey] === row[paramKey];
2475
- const getMiddleMonth = (minMonths, maxMonths) => minMonths + Math.round((maxMonths - minMonths) / 2);
2476
2350
 
2477
- const renderRegistrationType = (props) => renderBinaryRadioButtonGroup({
2478
- ...props,
2479
- items: [
2480
- { id: 'remote', text: 'Дистанционно' },
2481
- { id: 'office', text: 'Офис Банка' },
2482
- ],
2351
+ const ButtonsBlock = JSX(({ buttons, className = '', align, ...rest }) => {
2352
+ const alignStyle = align ? style('flex', AlignJustifyStyle[align]) : '';
2353
+ return (jsx(BlockWrapper, { className: style('!bg-transparent', alignStyle, className), defaultPadding: "p-0", ...rest, children: renderButtonsSection(buttons) }));
2483
2354
  });
2484
2355
 
2485
- const DepositCalculatorForm = JSX((props) => {
2486
- const { className, calcSource, buttons, footnote, paramsViewSettings, defaultParams } = props;
2487
- const [userInputParams, { field }] = useForm({
2488
- moneyValue: getMoneyDefaultValue(defaultParams, DEPOSIT_DEFAULT_SUM),
2489
- monthsValue: getMonthsDefaultValue(defaultParams),
2490
- isMonthlyInterestPayment: Boolean(defaultParams?.isMonthlyInterestPayment),
2491
- isRemoteRegistration: Boolean(defaultParams?.isRemoteRegistration),
2492
- });
2493
- const calculatorParams = getDepositCalculatorParams(userInputParams, calcSource, paramsViewSettings);
2494
- const { defaultMonth, rate, income, finalSum } = calculatorParams;
2495
- const { onChange: setMonthsValue } = field('monthsValue');
2496
- const { onChange: setMoneyValue } = field('moneyValue');
2497
- useEffect(() => {
2498
- !userInputParams.monthsValue && setMonthsValue && setMonthsValue(defaultMonth);
2499
- }, [defaultMonth]);
2500
- useEffect(() => {
2501
- setMoneyValue &&
2502
- setMoneyValue(clamp(userInputParams.moneyValue, calculatorParams.minSum, calculatorParams.maxSum));
2503
- }, [calculatorParams.minSum, calculatorParams.maxSum]);
2504
- return (jsx(CalculatorFormLayout, { className: className, inputChildren: jsxs("div", { children: [renderWantedSumInput({
2505
- title: 'Сумма, ₽',
2506
- min: calculatorParams.minSum,
2507
- max: calculatorParams.maxSum,
2508
- ...field('moneyValue'),
2509
- value: calculatorParams.moneyValue,
2510
- }), renderMonthsInput({
2511
- title: 'Срок, месяцев',
2512
- min: calculatorParams.minMonths,
2513
- max: calculatorParams.maxMonths,
2514
- availableMonths: calculatorParams.availableMonths,
2515
- ...field('monthsValue'),
2516
- value: calculatorParams.monthsValue,
2517
- }), renderDepositParamsForm({ ...calculatorParams, ...paramsViewSettings }, field)] }), rateChildren: renderRate({
2518
- rate,
2519
- }), resultChildren: jsxs("div", { children: [jsx(CalculatorValueBlock, { title: "\u0414\u043E\u0445\u043E\u0434 \u0432 \u043A\u043E\u043D\u0446\u0435 \u0441\u0440\u043E\u043A\u0430", value: income, postfix: "\u20BD", prefix: "+" }), jsx(CalculatorValueBlock, { title: "\u0421\u0443\u043C\u043C\u0430 \u0432 \u043A\u043E\u043D\u0446\u0435 \u0441\u0440\u043E\u043A\u0430", value: finalSum, postfix: "\u20BD" }), renderButtonsSection(buttons, { isVertical: true }), renderFootnote(footnote)] }) }));
2520
- });
2521
- const renderDepositParamsForm = (params, field) => (jsxs("div", { className: "flex flex-col gap-y-2xl", children: [params.isShowMonthlyInterestPayment
2522
- ? renderInterestPayment({
2523
- title: 'Выплата процентов',
2524
- ...field('isMonthlyInterestPayment'),
2525
- value: params.isMonthlyInterestPayment,
2526
- })
2527
- : null, params.isShowRemoteRegistration
2528
- ? renderRegistrationType({
2529
- title: 'Оформление',
2530
- ...field('isRemoteRegistration'),
2531
- value: params.isRemoteRegistration,
2532
- })
2533
- : null] }));
2356
+ /** @deprecated */
2357
+ function renderArrows({ dec, inc, canDec, canInc, btnClass, className = '', }) {
2358
+ const isVisible = canDec || canInc;
2359
+ if (!isVisible) {
2360
+ return null;
2361
+ }
2362
+ return (jsxs("div", { children: [canDec ? (jsx(ArrowButton, { className: style('absolute z-40', btnClass[0], className), onClick: dec, ariaLabel: "\u041F\u0440\u043E\u043B\u0438\u0441\u0442\u0430\u0442\u044C \u0432\u043B\u0435\u0432\u043E", "data-block-control": "scroll-left", disabled: !canDec })) : null, canInc ? (jsx(ArrowButton, { className: style('absolute z-40 rotate-180', btnClass[1], className), onClick: inc, ariaLabel: "\u041F\u0440\u043E\u043B\u0438\u0441\u0442\u0430\u0442\u044C \u0432\u043F\u0440\u0430\u0432\u043E", "data-block-control": "scroll-right", disabled: !canInc })) : null] }));
2363
+ }
2534
2364
 
2535
- const checkRefinancingSourceBookRowCommonParams = (props) => checkRowParam({
2536
- ...props,
2537
- fieldKey: 'isSalaryClient',
2538
- viewSettingFieldKey: 'isShowSalaryClient',
2539
- }) &&
2540
- checkRowParam({
2541
- ...props,
2542
- fieldKey: 'isInsurance',
2543
- viewSettingFieldKey: 'isShowInsurance',
2544
- });
2545
- const checkRowParam = ({ row, userInputParams, paramsViewSettings, fieldKey, viewSettingFieldKey, }) => {
2546
- const isExclude = paramsViewSettings ? !paramsViewSettings[viewSettingFieldKey] : false;
2547
- return isExclude || userInputParams[fieldKey] === row[fieldKey];
2548
- };
2365
+ const calculatorValueBlockStyle = 'flex justify-between @xl:flex-col';
2366
+ const CalculatorValueBlock = JSX(({ className, title, value, fractionDigits, fixed, prefix, postfix }) => (jsxs("div", { className: style(calculatorValueBlockStyle, className), children: [title ? (jsx(Text, { size: "text-l", font: "font-light", color: "text-secondary-text", children: title })) : null, value ? (jsx("div", { children: jsx(Text, { size: "text-h3", children: [prefix, toLocalNumberFormat(fractionDigits, { fixed })(value), postfix]
2367
+ .filter(Boolean)
2368
+ .join(' ') }) })) : (jsx("div", { children: jsx(Text, { size: "text-h3", children: "-" }) }))] })));
2549
2369
 
2550
- const getRefinancingCalculatorLimitParams = ({ limitRows, userInputParams, paramsViewSettings, }) => limitRows?.find((row) => checkRefinancingSourceBookRowCommonParams({
2551
- row,
2552
- userInputParams,
2553
- paramsViewSettings,
2554
- }));
2370
+ const DEFAULT_MIN_SUM$1 = 10000;
2371
+ const DEFAULT_MAX_SUM$1 = 5000000;
2372
+ const CREDIT_DEFAULT_SUM = 200000;
2373
+ const REFINANCING_DEFAULT_SUM = 700000;
2374
+ const REFINANCING_DEFAULT_MAX_SUM = 3000000;
2375
+ const REFINANCING_OLD_MIN_SUM = 0;
2376
+ const REFINANCING_OLD_DEFAULT_SUM = 0;
2377
+ const REFINANCING_OLD_MAX_SUM = 2500000;
2378
+ const REFINANCING_EXTRA_MIN_SUM = 0;
2379
+ const REFINANCING_EXTRA_MAX_SUM = 500000;
2380
+ const DEPOSIT_DEFAULT_SUM = 20000;
2381
+ const DEFAULT_MIN_MONTHS = 1;
2382
+ const DEFAULT_REFINANCING_MIN_MONTHS = 13;
2383
+ const DEFAULT_REFINANCING_MONTHS = 48;
2384
+ const DEFAULT_MAX_MONTHS = 60;
2385
+ const DEFAULT_MONTHS = Math.round((DEFAULT_MAX_MONTHS - DEFAULT_MIN_MONTHS) / 2);
2386
+ const BUSINESS_DEFAULT_SUM = 100000;
2387
+ const BUSINESS_MIN_DAYS = 1;
2388
+ const BUSINESS_MAX_DAYS = 1095;
2389
+ const BUSINESS_RATE_MIN_DAYS = 30; // порог количества дней, при которых ставка не доступна
2390
+ const BUSINESS_OPERATING_MIN_DAYS = 90;
2391
+ const BUSINESS_OPERATING_MAX_DAYS = 730;
2392
+ const BUSINESS_DEFAULT_DAYS = 60;
2393
+ const MONTHS_IN_YEAR = 12;
2394
+ const MONTHLY_INTEREST_RATE = MONTHS_IN_YEAR * 100;
2395
+ const DEFAULT_CREDIT_CALCULATOR_PARAMS = {
2396
+ minSum: DEFAULT_MIN_SUM$1,
2397
+ maxSum: DEFAULT_MAX_SUM$1,
2398
+ minMonths: DEFAULT_MIN_MONTHS,
2399
+ maxMonths: DEFAULT_MAX_MONTHS,
2400
+ rate: 0,
2401
+ isSalaryClient: false,
2402
+ isPensionClient: false,
2403
+ isStateEmployee: false,
2404
+ isInsurance: true,
2405
+ };
2406
+ const DEFAULT_REFINANCING_CALCULATOR_PARAMS = {
2407
+ minSum: DEFAULT_MIN_SUM$1,
2408
+ maxSum: REFINANCING_DEFAULT_MAX_SUM,
2409
+ minSumOld: REFINANCING_OLD_MIN_SUM,
2410
+ maxSumOld: REFINANCING_OLD_MAX_SUM,
2411
+ minSumExtra: REFINANCING_EXTRA_MIN_SUM,
2412
+ maxSumExtra: REFINANCING_EXTRA_MAX_SUM,
2413
+ minMonths: DEFAULT_REFINANCING_MIN_MONTHS,
2414
+ maxMonths: DEFAULT_MAX_MONTHS,
2415
+ rate: 0,
2416
+ isExtraMoney: false,
2417
+ isSalaryClient: false,
2418
+ isInsurance: true,
2419
+ };
2420
+ const DEFAULT_TRAVEL_MIN_SUM = 6000;
2421
+ const DEFAULT_REST_MIN_SUM = 6000;
2422
+ const DEFAULT_BONUS_CALCULATOR_PARAMS = {
2423
+ minSumTravel: 6000,
2424
+ maxSumTravel: 200000,
2425
+ minSumOther: 6000,
2426
+ maxSumOther: 200000,
2427
+ maxBonus: 5000,
2428
+ prefMonthsNum: 2,
2429
+ prefTravelBonusRate: 0.015,
2430
+ prefRestBonusRate: 0.01,
2431
+ travelBonusRate: 0.015,
2432
+ restBonusRate: 0.01,
2433
+ };
2434
+ const DEFAULT_DEPOSIT_CALCULATOR_PARAMS = {
2435
+ minSum: 50000,
2436
+ maxSum: 100000001,
2437
+ minMonths: 2,
2438
+ maxMonths: 10,
2439
+ rate: 0,
2440
+ isMonthlyInterestPayment: true,
2441
+ isRemoteRegistration: true,
2442
+ };
2443
+ const DEFAULT_BUSINESS_DEPOSIT_CALCULATOR_PARAMS = {
2444
+ minSum: 1000,
2445
+ maxSum: 100000001,
2446
+ minDays: BUSINESS_MIN_DAYS,
2447
+ maxDays: BUSINESS_MAX_DAYS,
2448
+ rate: 0,
2449
+ isMonthlyInterestPayment: true,
2450
+ isReplenished: false,
2451
+ isPartialWithdrawal: false,
2452
+ depositeName: "Депозит 'Стабильный'",
2453
+ };
2555
2454
 
2556
- const getRefinancingCalculatorRateParams = ({ rateRows = [], userInputParams, paramsViewSettings, moneyValue, monthsValue, oldMonthlyPayment, moneyExtra, }) => rateRows?.find((row) => checkRefinancingSourceBookRowCommonParams({
2557
- row,
2558
- userInputParams,
2559
- paramsViewSettings,
2560
- }) &&
2561
- checkRangeByKeys(moneyValue + moneyExtra, ['minSum', 'maxSum'], row) &&
2562
- checkRangeByKeys(oldMonthlyPayment, ['minSumOld', 'maxSumOld'], row) &&
2563
- checkRangeByKeys(moneyExtra, ['minSumExtra', 'maxSumExtra'], row) &&
2564
- checkRangeByKeys(monthsValue, ['minMonths', 'maxMonths'], row)) || DEFAULT_REFINANCING_CALCULATOR_PARAMS;
2455
+ const getBonus = (params, bonusTravelValue, bonusOtherValue) => {
2456
+ const { maxBonus = 5000, prefMonthsNum = DEFAULT_BONUS_CALCULATOR_PARAMS.prefMonthsNum, prefTravelBonusRate = DEFAULT_BONUS_CALCULATOR_PARAMS.prefTravelBonusRate, prefRestBonusRate = 0.01, travelBonusRate = 0.015, restBonusRate = 0.01, } = params;
2457
+ const prefBonus = Math.min(Math.round(prefTravelBonusRate * bonusTravelValue) +
2458
+ Math.round(prefRestBonusRate * bonusOtherValue), maxBonus);
2459
+ const bonus = Math.min(Math.round(travelBonusRate * bonusTravelValue) + Math.round(restBonusRate * bonusOtherValue), maxBonus);
2460
+ const yearBonus = prefBonus * prefMonthsNum + bonus * (MONTHS_IN_YEAR - prefMonthsNum);
2461
+ return [prefBonus, yearBonus];
2462
+ };
2565
2463
 
2566
- const getRefinancingCalculatorParams = (userInputParams, calcSource, paramsViewSettings) => {
2567
- const { moneyValue, monthsValue, oldMonthlyPayment, moneyExtra } = userInputParams;
2568
- const rateRows = calcSource?.rateRows || [DEFAULT_REFINANCING_CALCULATOR_PARAMS];
2569
- const limitRows = calcSource?.limitRows || [];
2570
- const sourceBookLimitParams = getRefinancingCalculatorLimitParams({
2571
- limitRows,
2572
- userInputParams,
2573
- paramsViewSettings,
2574
- });
2575
- const { minSum, maxSum } = getLimitRangeByKeys(['minSum', 'maxSum'], sourceBookLimitParams, rateRows);
2576
- const { minMonths, maxMonths } = getLimitRangeByKeys(['minMonths', 'maxMonths'], sourceBookLimitParams, rateRows);
2577
- const refinancingCalculatorSourceBookRateParams = getRefinancingCalculatorRateParams({
2578
- rateRows,
2579
- userInputParams,
2580
- paramsViewSettings,
2581
- moneyValue,
2582
- monthsValue,
2583
- oldMonthlyPayment,
2584
- moneyExtra,
2585
- });
2586
- const { rate } = refinancingCalculatorSourceBookRateParams;
2587
- const monthlyPayment = getMonthlyPayment(rate, userInputParams);
2588
- const benefit = userInputParams.oldMonthlyPayment - monthlyPayment;
2464
+ const getBonusCalculatorParams = (userInputParams, calcSource) => {
2465
+ const { travelExpenseValue, restExpenseValue } = userInputParams;
2466
+ const [monthBonus, yearBonus] = getBonus(calcSource || DEFAULT_BONUS_CALCULATOR_PARAMS, travelExpenseValue, restExpenseValue);
2589
2467
  return {
2590
- ...refinancingCalculatorSourceBookRateParams,
2468
+ ...calcSource,
2591
2469
  ...userInputParams,
2592
- minSum,
2593
- maxSum,
2594
- minMonths,
2595
- maxMonths,
2596
- moneyValue,
2597
- monthsValue,
2598
- oldMonthlyPayment,
2599
- moneyExtra,
2600
- monthlyPayment,
2601
- benefit,
2470
+ monthBonus,
2471
+ yearBonus,
2602
2472
  };
2603
2473
  };
2604
2474
 
2605
- const RefinancingCalculatorForm = JSX(({ className, calcSource, buttons, footnote, paramsViewSettings }) => {
2475
+ const renderFootnote = (text) => text ? (jsx(Paragraph, { size: "text-s", font: "font-light", color: "text-secondary-text", children: text })) : null;
2476
+
2477
+ const Rate = JSX(({ rate = 0, title = 'Ставка', unit, fractionDigits = 2, depositeName, isShowDepositeName }) => (jsxs("div", { children: [jsx(Text, { size: "text-l", font: "font-light", color: "text-secondary-text", children: title }), jsxs("div", { className: "relative", children: [jsxs("div", { className: "flex justify-start gradient-color-text", children: [jsx("span", { className: "font-mohave text-title-huge pr-xl -mt-m tracking-[-15px]", children: toLocalNumberFormat(fractionDigits, { fixed: true })(rate) }), unit ? jsx("span", { className: "text-7xl", children: unit }) : null] }), depositeName && isShowDepositeName ? (jsx("div", { className: "absolute bottom-2 w-full text-left whitespace-nowrap", children: depositeName })) : null] })] })));
2478
+
2479
+ const UnknownRate = JSX(({ title = 'Ставка', isUnavailableByTerm = false }) => (jsxs("div", { className: "space-y-s", children: [jsx(Text, { size: "text-l", font: "font-light", color: "text-secondary-text", children: title }), jsx(Icon, { className: "w-10 h-10 @5xl:h-auto @5xl:w-40", name: "PercentIcon" }), jsx("div", { className: "w-64", children: isUnavailableByTerm ? (jsxs(Paragraph, { size: "text-xl", font: "font-light", color: "text-primary-text", children: ["\u041F\u0440\u043E\u0446\u0435\u043D\u0442\u043D\u0430\u044F \u0441\u0442\u0430\u0432\u043A\u0430 \u0434\u043B\u044F \u0441\u0440\u043E\u043A\u0430 ", jsx("strong", { children: "1-30 \u0434\u043D\u0435\u0439" }), " \u0443\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0435\u0442\u0441\u044F \u043F\u0440\u0438 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0438 \u0432 \u0411\u0430\u043D\u043A"] })) : (jsx(Paragraph, { size: "text-xl", font: "font-light", color: "text-primary-text", children: "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0434\u0440\u0443\u0433\u0443\u044E \u0441\u0443\u043C\u043C\u0443 \u0438\u043B\u0438 \u0441\u0440\u043E\u043A" })) })] })));
2480
+
2481
+ const renderRate = ({ title = 'Ставка', rate = 0, unit = '%', fractionDigits = 2, isUnknownRateShown = true, ...rest }) => isUnknownRateShown && rate === 0 ? (jsx(UnknownRate, { title: title, ...rest })) : (jsxs("div", { children: [jsx("div", { className: "hidden @xl:block", children: jsx(Rate, { title: title, rate: rate, unit: unit, fractionDigits: fractionDigits, ...rest }) }), jsx("div", { className: "@xl:hidden", children: jsx(CalculatorValueBlock, { title: title, value: rate, postfix: unit, fractionDigits: fractionDigits, fixed: true, ...rest }) })] }));
2482
+
2483
+ const DEFAULT_MONEY_STEP = 1000;
2484
+ function renderWantedSumInput({ isShowItems = true, step = DEFAULT_MONEY_STEP, ...rest }) {
2485
+ const items = [
2486
+ `От ${toLocalNumberFormat(2)(rest.min || 0)} ₽`,
2487
+ `До ${toLocalNumberFormat(2)(rest.max || 0)} ₽`,
2488
+ ];
2489
+ return jsx(InputRange, { items: isShowItems ? items : [], step: step, ...rest });
2490
+ }
2491
+
2492
+ const BonusCalculatorForm = JSX(({ className = '', calcSource, buttons, footnote }) => {
2606
2493
  const [userInputParams, { field }] = useForm({
2607
- isAnnuity: true,
2608
- isInsurance: true,
2609
- isSalaryClient: false,
2610
- isExtraMoney: false,
2611
- moneyValue: REFINANCING_DEFAULT_SUM,
2612
- oldMonthlyPayment: REFINANCING_OLD_DEFAULT_SUM,
2613
- monthsValue: DEFAULT_REFINANCING_MONTHS,
2614
- moneyExtra: REFINANCING_OLD_DEFAULT_SUM,
2615
- });
2616
- const calculatorParams = getRefinancingCalculatorParams(userInputParams, calcSource, paramsViewSettings);
2617
- const paymentTypeElement = renderPaymentType({
2618
- title: '',
2619
- mobileTitle: 'Тип платежа',
2620
- orientation: 'vertical',
2621
- ...field('isAnnuity'),
2494
+ travelExpenseValue: DEFAULT_TRAVEL_MIN_SUM,
2495
+ restExpenseValue: DEFAULT_REST_MIN_SUM,
2622
2496
  });
2623
- return (jsx(CalculatorFormLayout, { className: className, inputChildren: jsxs("div", { children: [renderWantedSumInput({
2624
- title: 'Остаток долга в других банках, ₽ ',
2625
- step: 500,
2626
- min: calculatorParams.minSum,
2627
- max: calculatorParams.maxSum - calculatorParams.maxSumExtra,
2628
- ...field('moneyValue'),
2629
- value: calculatorParams.moneyValue,
2630
- }), renderWantedSumInput({
2631
- title: 'Ежемесячный платёж в других банках, ₽',
2632
- step: 500,
2633
- min: calculatorParams.minSumOld,
2634
- max: calculatorParams.maxSumOld,
2635
- ...field('oldMonthlyPayment'),
2636
- value: calculatorParams.oldMonthlyPayment,
2637
- }), renderMonthsInput({
2638
- title: 'Новый срок кредита, месяцев',
2639
- min: calculatorParams.minMonths,
2640
- max: calculatorParams.maxMonths,
2641
- ...field('monthsValue'),
2642
- value: calculatorParams.monthsValue,
2643
- }), renderCreditParamsForm({ ...calculatorParams, ...paramsViewSettings }, field), calculatorParams.isExtraMoney
2644
- ? renderWantedSumInput({
2645
- title: 'Дополнительная сумма, ₽ ',
2497
+ const { minSumTravel, maxSumTravel, minSumOther, maxSumOther, monthBonus, yearBonus } = getBonusCalculatorParams(userInputParams, calcSource);
2498
+ return (jsxs("section", { className: style('flex flex-col justify-between @5xl:flex-row gap-y-4xl', className), children: [jsxs("div", { className: "flex flex-col @xl:max-w-md gap-2xl border-b border-b-main-divider @xl:border-none pb-3xl @xl:pb-0", children: [renderWantedSumInput({
2499
+ title: 'Сумма покупок в категории путешествия, ₽',
2500
+ min: minSumTravel,
2501
+ max: maxSumTravel,
2646
2502
  step: 500,
2647
- min: calculatorParams.minSumExtra,
2648
- max: calculatorParams.maxSumExtra,
2649
- ...field('moneyExtra'),
2650
- value: calculatorParams.moneyExtra,
2651
- })
2652
- : null, jsx("div", { className: "sm:hidden pt-1 pb-4 border-b border-main-divider", children: paymentTypeElement })] }), rateChildren: renderRate({
2653
- rate: calculatorParams.rate,
2654
- fractionDigits: 1,
2655
- }), resultChildren: jsxs("div", { children: [jsx(CalculatorValueBlock, { title: "\u0415\u0436\u0435\u043C\u0435\u0441\u044F\u0447\u043D\u044B\u0439 \u043F\u043B\u0430\u0442\u0451\u0436", value: calculatorParams.monthlyPayment, postfix: "\u20BD" }), calculatorParams.benefit > 0 ? (jsx(CalculatorValueBlock, { title: 'Выгода в месяц', value: calculatorParams.benefit, postfix: "\u20BD" })) : null, jsx("div", { className: "hidden sm:block mb-lg", children: paymentTypeElement }), renderButtonsSection(buttons, { isVertical: true }), renderFootnote(footnote)] }) }));
2503
+ isShowItems: false,
2504
+ ...field('travelExpenseValue'),
2505
+ }), renderWantedSumInput({
2506
+ title: 'Сумма покупок в остальных категориях, ₽',
2507
+ min: minSumOther,
2508
+ max: maxSumOther,
2509
+ step: 500,
2510
+ isShowItems: false,
2511
+ ...field('restExpenseValue'),
2512
+ })] }), monthBonus >= 0
2513
+ ? renderRate({
2514
+ title: 'Баллов за месяц',
2515
+ rate: monthBonus,
2516
+ fractionDigits: 0,
2517
+ isUnknownRateShown: false,
2518
+ unit: '',
2519
+ })
2520
+ : null, jsxs("div", { className: "flex flex-col gap-y-xl @xl:w-96", children: [yearBonus >= 0 ? jsx(CalculatorValueBlock, { title: "\u0411\u0430\u043B\u043B\u043E\u0432 \u0437\u0430 \u0433\u043E\u0434", value: yearBonus }) : null, renderButtonsSection(buttons, { isVertical: true }), renderFootnote(footnote)] })] }));
2656
2521
  });
2657
- const renderCreditParamsForm = (params, field) => (jsxs("div", { className: "space-y-lg sm:space-y-s", children: [params.isShowSalaryClient ? (jsx(Checkbox, { text: "\u041F\u043E\u043B\u0443\u0447\u0430\u044E \u0437\u0430\u0440\u043F\u043B\u0430\u0442\u0443 \u043D\u0430 \u0441\u0447\u0451\u0442 \u0432 \u0420\u043E\u0441\u0441\u0435\u043B\u044C\u0445\u043E\u0437\u0431\u0430\u043D\u043A\u0435", ...field('isSalaryClient'), value: params.isSalaryClient })) : null, params.isShowInsurance ? (jsx(Checkbox, { text: "\u041A\u043E\u043C\u043F\u043B\u0435\u043A\u0441\u043D\u0430\u044F \u0441\u0442\u0440\u0430\u0445\u043E\u0432\u0430\u044F \u0437\u0430\u0449\u0438\u0442\u0430", ...field('isInsurance'), value: params.isInsurance })) : null, jsx(Checkbox, { text: "\u041F\u043E\u043B\u0443\u0447\u0438\u0442\u044C \u0434\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u0443\u044E \u0441\u0443\u043C\u043C\u0443", ...field('isExtraMoney'), value: params.isExtraMoney })] }));
2658
-
2659
- const EmbeddableCalcBlocks = {
2660
- CreditCalculatorForm,
2661
- DepositCalculatorForm,
2662
- BusinessDepositCalculatorForm,
2663
- BonusCalculatorForm,
2664
- RefinancingCalculatorForm,
2665
- };
2666
2522
 
2667
- const renderCalculatorBlock = (block, i) => {
2668
- const type = block?.calcType;
2669
- if (!type || !(type in EmbeddableCalcBlocks)) {
2670
- return null;
2671
- }
2672
- const EmbeddedBlock = EmbeddableCalcBlocks[type];
2673
- return (jsx("section", { className: "box-border relative bg-white flex shrink-0 self-start basis-full", role: "listitem", children: jsxs("div", { className: "box-border p-4xl w-full space-y-xl", children: [block?.title ? jsx(Heading, { className: "text-center", title: block.title }) : null, jsx(EmbeddedBlock, { ...block })] }) }, String(i)));
2674
- };
2523
+ const CalculatorFormLayout = JSX(({ className, inputChildren, rateChildren, resultChildren }) => (jsxs("section", { className: style('flex flex-col justify-between @5xl:flex-row gap-m', className), children: [jsx("div", { className: "flex flex-col gap-y-m sm:max-w-md gap-2xl", children: inputChildren }), jsx("div", { className: "border-b border-main-divider md:hidden" }), rateChildren, jsx("div", { className: "flex flex-col gap-2xl sm:w-96 max-w-full", children: resultChildren })] })));
2675
2524
 
2676
- const Calculator = UniBlock(({ className = '', tabAlign, calculatorTabs = [], ...rest }) => {
2677
- const tabsNav = calculatorTabs.map((_) => _.nav || {});
2678
- const calculatorBlocks = calculatorTabs.map(({ calculatorBlock }) => calculatorBlock || {});
2679
- const [slideIndexShift, setSlideIndexShift] = useState(0);
2680
- const handleSlideIndexShift = useCallback((_) => setSlideIndexShift(_), []);
2681
- const COLUMNS_FOR_SCROLL = 3;
2682
- const [activeIndex, controls] = useCarouselControls({
2683
- itemCount: calculatorTabs?.length,
2684
- visibleItemCount: COLUMNS_FOR_SCROLL,
2685
- });
2686
- return (jsxs(BlockWrapper, { className: style('box-border overflow-hidden relative', className), defaultPadding: "p-0", ...rest, children: [jsxs("div", { className: "bg-white", children: [tabsNav.length > 1
2687
- ? renderTabs$1({
2688
- activeIndex,
2689
- tabsNav,
2690
- tabAlign,
2691
- slideIndexShift,
2692
- onClick: handleSlideIndexShift,
2693
- })
2694
- : null, renderArrows({
2695
- ...controls,
2696
- btnClass: ['left-3', 'right-3'],
2697
- className: 'top-10',
2698
- })] }), jsx("div", { className: "flex", style: { transform: `translateX(-${slideIndexShift}00%)` }, role: "list", children: calculatorBlocks?.length ? calculatorBlocks.map(renderCalculatorBlock) : null })] }));
2699
- });
2700
- function renderNavButton$3({ tab, i, isActive, onClick }) {
2701
- const btnClassName = isActive
2702
- ? 'w-96 p-m border-none bg-primary-main text-white'
2703
- : `w-96 p-s ease-in duration-300 bg-white text-primary-text`;
2704
- const btnTitleClassName = isActive ? 'text-xl mb-xs' : `text-l mb-2xs`;
2705
- const btnDescClassName = isActive
2706
- ? 'text-l font-light text-white'
2707
- : `text-m font-light text-secondary-text`;
2708
- const iconClassName = isActive ? '' : `text-primary-main`;
2709
- const iconVersion = tab?.icon?.iconVersion;
2710
- const activeIconStyle = isActive ? 'white' : 'normal';
2711
- return (jsxs("button", { type: "button", onClick: onClick, "aria-label": tab?.title, className: style('box-border relative border border-gray cursor-pointer text-left border-b-0 flex space-x-m min-h-20', btnClassName), children: [jsx(Img, { className: style('h-6 w-6 min-w-6 min-h-6', iconClassName), image: {
2712
- ...tab?.icon,
2713
- iconVersion: iconVersion ?? activeIconStyle,
2714
- }, width: "24", height: "24" }), jsxs("div", { className: "border-0", children: [jsx("div", { className: btnTitleClassName, children: tab?.title }), jsx("div", { className: btnDescClassName, children: tab.description })] })] }, String(i)));
2715
- }
2716
- function renderTabs$1({ activeIndex, tabsNav, tabAlign, slideIndexShift, onClick }) {
2717
- const CARD_SHIFT = 388;
2718
- const alignStyle = tabAlign ? AlignJustifyStyle[tabAlign] : '';
2719
- return (jsxs("div", { children: [jsx("div", { style: { transform: `translateX(-${activeIndex * CARD_SHIFT}px)` }, className: "flex flex-col justify-end items-center h-32 pt-4xl box-border duration-1000", children: jsx("div", { className: style('w-full flex flex-nowrap items-end gap-xs', alignStyle), children: tabsNav.map((tab, i) => renderNavButton$3({
2720
- tab,
2721
- i,
2722
- isActive: Boolean(i === slideIndexShift),
2723
- onClick: () => onClick(i),
2724
- })) }) }), jsx("div", { className: "w-full h-2 bg-primary-main" })] }));
2725
- }
2726
-
2727
- const NumberInput = JSX((props) => {
2728
- const { onChange, min, max, defaultValue = '0', fractionDigits = 0, ...inputProps } = props;
2729
- const normalizedProps = useNormalizedInput(props, {
2730
- normalize: (_ = defaultValue) => min && max ? String(clamp(Number(sanitizeNumberStr(_)), min, max)) : sanitizeNumberStr(_),
2731
- format: toLocalNumberFormat(fractionDigits),
2732
- });
2733
- const handleChange = useCallback((_) => onChange && onChange(sanitizeNumberStr(_)), [onChange]);
2734
- return jsx(Input, { onChange: handleChange, ...inputProps, ...normalizedProps });
2735
- });
2736
- const sanitizeNumberStr = (_ = '') => _.replaceAll(',', '.')
2737
- .replace(/[^\d.]/g, '')
2738
- .replace(/([^.]*\.[^.]*)\./, '$1');
2525
+ const getMoneyDefaultValue = (defaultParams, defaultMoney = 0) => defaultParams?.sum ? defaultParams.sum : defaultMoney;
2526
+ const getMonthsDefaultValue = (defaultParams, defaultMonth = 0) => defaultParams?.period &&
2527
+ defaultParams.period >= DEFAULT_MIN_MONTHS &&
2528
+ defaultParams.period <= DEFAULT_MAX_MONTHS
2529
+ ? defaultParams.period
2530
+ : defaultMonth;
2739
2531
 
2740
- const CurrencyInput = JSX((props) => (jsx(NumberInput, { ...props, fractionDigits: 2, children: jsx("div", { className: "absolute right-4 bottom-4", children: jsx(Text, { size: "text-xl", font: "font-light", children: "\u20BD" }) }) })));
2532
+ const STEP_DAYS = 1;
2533
+ const renderDaysInput = (props) => (jsx(InputRange, { items: [`От ${daysText(props.min || 0)}`, `До ${daysText(props.max || 0)}`], step: STEP_DAYS, ...props }));
2534
+ const daysText = (days) => `${days} ${Number(days) % 10 === 1 ? 'дня' : 'дней'}`;
2741
2535
 
2742
- const DEFAULT_TITLE = 'Укажите сумму перевода';
2743
- const DEFAULT_LABEL = 'Сумма перевода';
2744
- const DEFAULT_PLACEHOLDER = '1500';
2745
- const MAX_AMOUNT = 300000;
2746
- const MIN_AMOUNT = 1;
2747
- const FORM_URL_MAP = {
2748
- private: 'https://old.rshb.ru/p2p/',
2749
- business: 'https://old.rshb.ru/smallbusiness/p2p/',
2750
- };
2751
- const INITIAL_FORM_STATE = {
2752
- amount: 1500,
2536
+ const orientationStyleMap = {
2537
+ horizontal: 'gap-5xl',
2538
+ vertical: 'flex-col gap-s',
2753
2539
  };
2754
- const CardTransfer = JSX(({ className = '', title = DEFAULT_TITLE, label = DEFAULT_LABEL, placeholder = DEFAULT_PLACEHOLDER, button, formType = 'private', ...rest }) => {
2755
- const [{ amount }, { field }] = useForm(INITIAL_FORM_STATE);
2756
- const [isDisabled, setDisabled] = useState(false);
2757
- useEffect(() => {
2758
- setDisabled(amount <= 0);
2759
- }, [amount]);
2760
- return (jsxs(BlockWrapper, { className: style('flex flex-col items-center', className), defaultPadding: "p-6xl", ...rest, children: [title ? (jsx(Heading, { className: "pb-lg xl:pb-4xl self-start xl:self-center", headingType: "h3", title: title })) : null, jsxs("form", { className: "w-full xl:max-w-[468px]", method: "POST", action: FORM_URL_MAP[formType], children: [jsx(CurrencyInput, { valid: !isDisabled, placeholder: placeholder, label: label, max: MAX_AMOUNT, min: MIN_AMOUNT, ...field('amount', { parse: Number, format: String }) }), jsx("input", { type: "hidden", name: "amount", value: amount }), button?.text ? (jsx(Button, { className: "w-full mt-lg", type: "submit", disabled: isDisabled, children: button.text })) : null] })] }));
2540
+ const RadioButtonGroup = JSX(({ items, checkedItem, className, onChange: onChangeCheckedItem, orientation = 'horizontal', }) => (jsx("div", { className: style(`flex ${orientationStyleMap[orientation]}`, className), role: "radiogroup", children: items?.map(({ id, text }) => (jsx(Checkbox, { isRadio: true, text: text, value: checkedItem === id, onChange: () => onChangeCheckedItem && onChangeCheckedItem(id) }, String(id)))) })));
2541
+
2542
+ const renderBinaryRadioButtonGroup = ({ items, title, mobileTitle = title, orientation = 'horizontal', value, radioGroupClassName, onChange, }) => (jsxs("div", { className: "space-y-s", children: [title ? (jsx("div", { className: "hidden @5xl:block", children: jsx(Text, { color: "text-secondary-text", font: "font-light", children: title }) })) : null, mobileTitle ? (jsx("div", { className: "@5xl:hidden", children: jsx(Text, { color: "text-secondary-text", font: "font-light", children: mobileTitle }) })) : null, jsx(RadioButtonGroup, { orientation: orientation, items: items, checkedItem: items[value ? 0 : 1].id, className: style('flex-col sm:flex-row', radioGroupClassName), onChange: (_) => onChange && onChange(_ === items[0].id) })] }));
2543
+
2544
+ const renderInterestPayment = (props) => renderBinaryRadioButtonGroup({
2545
+ ...props,
2546
+ items: [
2547
+ { id: 'everyMonth', text: 'Ежемесячно' },
2548
+ { id: 'atEndTerm', text: 'В конце срока' },
2549
+ ],
2761
2550
  });
2762
2551
 
2763
- const mapChildren = (mapper) => (children) => {
2764
- if (children === null || children === undefined) {
2765
- return children;
2552
+ const getLimitRangeByKeys = ([minKey, maxKey], limitParams, rateRows = []) => limitParams && limitParams[minKey] && limitParams[maxKey]
2553
+ ? {
2554
+ [minKey]: limitParams[minKey],
2555
+ [maxKey]: limitParams[maxKey],
2766
2556
  }
2767
- return Array.isArray(children) ? children.map(mapper) : mapper(children, 0);
2768
- };
2769
-
2770
- const HEADER_HEIGHT = 170;
2771
- const FOOTER_HEIGHT = 48;
2772
- const BLOCKS_GAP = 4;
2773
- const PAGE_PADDINGS_X = 20;
2774
- const DEFAULT_HEIGHT = 866;
2775
- const getContainerHeight = (minHeight) => ({
2776
- height: `calc(100vh - ${HEADER_HEIGHT + FOOTER_HEIGHT + BLOCKS_GAP + PAGE_PADDINGS_X}px)`,
2777
- minHeight: `${minHeight || DEFAULT_HEIGHT}px`,
2557
+ : getRangeByKeys([minKey, maxKey], rateRows);
2558
+ const getRangeByKeys = ([minKey, maxKey], rows = []) => ({
2559
+ [minKey]: rows.length
2560
+ ? min(rows.filter((row) => typeof row[minKey] === 'number').map((row) => row[minKey]))
2561
+ : 0,
2562
+ [maxKey]: rows.length
2563
+ ? max(rows.filter((row) => typeof row[minKey] === 'number').map((row) => row[maxKey]))
2564
+ : 0,
2778
2565
  });
2566
+ const checkRangeByKeys = (value, [minKey, maxKey], sourceBookRow) => value >= sourceBookRow[minKey] && value <= sourceBookRow[maxKey];
2779
2567
 
2780
- const SENSITIVITY_SCALE = 10;
2781
- function useIntersectionObserver(observerCallback, { sensitivity, rootRef } = {}) {
2782
- const ref = useRef(null);
2783
- const observerOptions = useMemo(() => ({ threshold: sensitivityToThreshold(sensitivity), root: rootRef?.current }), [sensitivity, rootRef?.current]);
2784
- useEffect(() => {
2785
- if (!ref.current) {
2786
- return undefined;
2787
- }
2788
- const intersectionObserver = new IntersectionObserver(observerCallback, observerOptions);
2789
- intersectionObserver.observe(ref.current);
2790
- return () => {
2791
- intersectionObserver.disconnect();
2792
- };
2793
- }, [observerCallback, observerOptions]);
2794
- return ref;
2795
- }
2796
- function sensitivityToThreshold(sensitivity = 0) {
2797
- const thresholdsCount = Math.round(sensitivity * SENSITIVITY_SCALE);
2798
- return Array.from({ length: thresholdsCount })
2799
- .fill(0)
2800
- .map((_, thi) => Math.min(thi / thresholdsCount), 1)
2801
- .concat(1);
2568
+ const DEPOSIT_RATES_SERVICE_MAP = {
2569
+ UL: 'businessdepositrates',
2570
+ IP: 'selfemployeddepositrates',
2571
+ };
2572
+ function useBusinessDepositRates(rateParams) {
2573
+ const serviceName = rateParams?.rateType ? DEPOSIT_RATES_SERVICE_MAP[rateParams?.rateType] : '';
2574
+ const { data } = useAsyncData(serviceName ? `/api/v1/depositrates/${serviceName}` : null, fetchJSONUnsafe);
2575
+ return Array.isArray(data) ? data : [];
2802
2576
  }
2803
2577
 
2804
- const SwipeListItem = JSX(({ className, style: inlineStyle, idx, activeIndex, observerOptions, children, onIntersection, }) => {
2805
- const observerCallback = useCallback((entries) => {
2806
- if (entries.length && onIntersection) {
2807
- onIntersection(idx || 0, entries[entries.length - 1]);
2808
- }
2809
- }, [onIntersection, idx]);
2810
- const ref = useIntersectionObserver(observerCallback, observerOptions);
2811
- return (jsx("div", { className: style('inline-block snap-always box-border', className), style: inlineStyle, "aria-current": Boolean(idx === activeIndex), ref: ref, role: "listitem", children: children }));
2812
- });
2813
-
2814
- const childStyleMap = ['min-w-full', '@lg:min-w-[50%]', '@lg:min-w-[33.3%]', '@lg:min-w-[25%]'];
2815
- const SwipeListContainer = JSX(({ className, itemClassName, containerRef, activeIndex, visibleItemCount = 0, gap = 0, padding = 0, listType = 'horizontal-list', snapAlign = 'snap-center', minHeight, children, onVisibleItemsChange, onVisibleIndicesChange, }) => {
2816
- const observerOptions = {
2817
- sensitivity: 1,
2818
- rootRef: containerRef,
2819
- };
2820
- const itemsVisibilityListRef = useRef([]);
2821
- const handleIntersection = useCallback((idx, entry) => {
2822
- const itemsVisibilityList = [...(itemsVisibilityListRef.current || [])];
2823
- itemsVisibilityList[idx] = entry;
2824
- itemsVisibilityListRef.current = itemsVisibilityList;
2825
- onVisibleItemsChange && onVisibleItemsChange(itemsVisibilityList);
2826
- onVisibleIndicesChange && onVisibleIndicesChange(getVisibleIndices(itemsVisibilityList));
2827
- }, [onVisibleItemsChange, onVisibleIndicesChange]);
2828
- const additionPadding = visibleItemCount > 0 ? gap * Number(visibleItemCount) - 1 : 0;
2829
- const isVerticalList = listType === 'vertical-list';
2830
- const isHorizontalList = listType === 'horizontal-list';
2831
- const containerStyle = {
2832
- ...(isHorizontalList ? getContainerOffset(padding, additionPadding) : {}),
2833
- ...(isVerticalList ? getContainerHeight(minHeight) : {}),
2834
- gap: `${gap}px`,
2578
+ const useBusinessDepositCalculatorParams = (userInputParams, rateParams) => {
2579
+ const { daysValue, moneyValue, isPartialWithdrawal, isReplenished, isMonthlyInterestPayment } = userInputParams;
2580
+ const isOperatingDeposit = isReplenished || isPartialWithdrawal;
2581
+ const isDisabledOperatingDeposit = daysValue < BUSINESS_OPERATING_MIN_DAYS || daysValue > BUSINESS_OPERATING_MAX_DAYS;
2582
+ const rateRows = useBusinessDepositRates(rateParams);
2583
+ if (daysValue <= BUSINESS_RATE_MIN_DAYS) {
2584
+ return {
2585
+ ...DEFAULT_BUSINESS_DEPOSIT_CALCULATOR_PARAMS,
2586
+ ...userInputParams,
2587
+ isDisabledOperatingDeposit,
2588
+ isUnavailableByTerm: true,
2589
+ };
2590
+ }
2591
+ const businessDepositCalculatorSourceBookParams = rateRows?.find((_) => {
2592
+ return (isMonthlyInterestPayment === _.isMonthlyInterestPayment &&
2593
+ checkIsOperatingDeposit(isOperatingDeposit, _) &&
2594
+ checkRangeByKeys(moneyValue, ['minSum', 'maxSum'], _) &&
2595
+ checkRangeByKeys(daysValue, ['minDays', 'maxDays'], _));
2596
+ }) || DEFAULT_BUSINESS_DEPOSIT_CALCULATOR_PARAMS;
2597
+ const { rate } = businessDepositCalculatorSourceBookParams;
2598
+ const income = getDepositIncome$1(rate, userInputParams);
2599
+ const finalSum = moneyValue + income;
2600
+ const sourceBookRows = rateRows || [DEFAULT_BUSINESS_DEPOSIT_CALCULATOR_PARAMS];
2601
+ return {
2602
+ ...businessDepositCalculatorSourceBookParams,
2603
+ ...getRangeByKeys(['minSum', 'maxSum'], sourceBookRows),
2604
+ ...getRangeByKeys(['minDays', 'maxDays'], sourceBookRows),
2605
+ ...userInputParams,
2606
+ isDisabledOperatingDeposit,
2607
+ income,
2608
+ finalSum,
2835
2609
  };
2836
- return (jsx("div", { className: style('box-border overflow-auto no-scrollbar', listType, className), style: containerStyle, ref: containerRef, role: "list", children: mapChildren((child, idx) => (jsx(SwipeListItem, { className: style(snapAlign, visibleItemCount ? childStyleMap[visibleItemCount - 1] : '', isVerticalList ? 'h-full' : '', itemClassName), style: isHorizontalList ? getContainerOffset(padding / 4) : {}, activeIndex: activeIndex, idx: idx, observerOptions: observerOptions, onIntersection: handleIntersection, children: child }, String(idx))))(children) }));
2837
- });
2838
- // Styles used to compensate outer container padding to get rid of clipping effect
2839
- const getContainerOffset = (padding, additionalPadding = 0) => ({
2840
- marginLeft: `${-padding}px`,
2841
- marginRight: `${-padding}px`,
2842
- paddingLeft: `${padding}px`,
2843
- paddingRight: `${padding + additionalPadding}px`,
2844
- });
2845
- const getVisibleIndices = (itemsVisibilityList) => itemsVisibilityList
2846
- .map((_, i) => [_, i])
2847
- .filter(([_]) => _ && _.intersectionRatio >= 0.9)
2848
- .map(([, i]) => i);
2849
-
2850
- const containerStyles = {
2851
- 'horizontal-list': 'mx-auto mt-lg w-fit',
2852
- 'vertical-list': 'absolute flex-col justify-center h-full top-0 left-4',
2853
2610
  };
2854
- const activeDotStyles = {
2855
- 'horizontal-list': 'w-6',
2856
- 'vertical-list': 'h-6',
2611
+ const checkIsOperatingDeposit = (isOperatingDeposit, sourceBookRow) => isOperatingDeposit
2612
+ ? sourceBookRow.isReplenished && sourceBookRow.isPartialWithdrawal
2613
+ : !sourceBookRow.isReplenished && !sourceBookRow.isPartialWithdrawal;
2614
+ const getDepositIncome$1 = (rate, userInputParams) => {
2615
+ const { isMonthlyInterestPayment, moneyValue, daysValue } = userInputParams;
2616
+ /*
2617
+ Приблизительный расчёт, не учитывающий разницу в количестве дней в месяцах. Месяц принят за 30 дней, год за 365 дней.
2618
+ */
2619
+ if (isMonthlyInterestPayment) {
2620
+ return Math.round(moneyValue * Math.pow((1200 + rate) / 1200, daysValue / 30) - moneyValue);
2621
+ }
2622
+ else {
2623
+ return Math.round((moneyValue * rate * daysValue) / 365 / 100);
2624
+ }
2857
2625
  };
2858
- const dotStyle = 'cursor-pointer rounded-full transition-width duration-300 bg-primary-main group-data-secondary:bg-white group-data-transparent:bg-white';
2859
- const inactiveDotStyle = 'w-2 h-2 opacity-30';
2860
- const SwipeListControlDots = JSX(({ className = '', containerRef, activeIndex, dotCount = 0, listType = 'horizontal-list', version, }) => {
2861
- const handleClick = (idx) => {
2862
- if (idx !== activeIndex && containerRef?.current) {
2863
- containerRef.current.children[idx]?.scrollIntoView({
2864
- behavior: 'smooth',
2865
- block: 'nearest',
2866
- });
2867
- }
2868
- };
2869
- return dotCount ? (jsx("div", { className: style('flex gap-xs', containerStyles[listType], version ? 'group' : '', className), "data-ver": version, children: Array.from({ length: dotCount })
2870
- .fill(null)
2871
- .map((_, idx) => (jsx("div", { className: `${dotStyle} ${idx === activeIndex ? activeDotStyles[listType] : inactiveDotStyle}`, role: "button", "aria-hidden": true, onClick: () => handleClick(idx) }, String(idx)))) })) : null;
2872
- });
2873
2626
 
2874
- const areArraysEqual = (as, bs) => as === bs || Boolean(as && bs && as?.length === bs?.length && as.every((_, i) => _ === bs[i]));
2627
+ const BusinessDepositCalculatorForm = JSX(({ className = '', buttons, footnote, bottomFootnote, isShowDepositeName, defaultParams, rateParams, }) => {
2628
+ const [userInputParams, { field }] = useForm({
2629
+ moneyValue: getMoneyDefaultValue(defaultParams, BUSINESS_DEFAULT_SUM),
2630
+ daysValue: getMonthsDefaultValue(defaultParams, BUSINESS_DEFAULT_DAYS),
2631
+ isReplenished: Boolean(defaultParams?.isReplenished),
2632
+ isPartialWithdrawal: Boolean(defaultParams?.isPartialWithdrawal),
2633
+ isMonthlyInterestPayment: Boolean(defaultParams?.isMonthlyInterestPayment),
2634
+ });
2635
+ const calculatorParams = useBusinessDepositCalculatorParams(userInputParams, rateParams);
2636
+ const { income, finalSum, rate, isUnavailableByTerm, depositeName } = calculatorParams;
2637
+ return (jsx(CalculatorFormLayout, { className: className, inputChildren: jsxs("div", { children: [renderWantedSumInput({
2638
+ title: 'Сумма, ₽',
2639
+ min: calculatorParams.minSum,
2640
+ max: calculatorParams.maxSum,
2641
+ ...field('moneyValue'),
2642
+ value: calculatorParams.moneyValue,
2643
+ }), renderDaysInput({
2644
+ title: 'Срок, дней',
2645
+ min: calculatorParams.minDays,
2646
+ max: calculatorParams.maxDays,
2647
+ ...field('daysValue'),
2648
+ value: calculatorParams.daysValue,
2649
+ }), jsxs("div", { className: "flex flex-col sm:flex-row gap-y-4 gap-x-14", children: [renderBusinessDepositParamsForm(calculatorParams, field), renderInterestPayment({
2650
+ title: 'Выплата процентов',
2651
+ orientation: 'vertical',
2652
+ ...field('isMonthlyInterestPayment'),
2653
+ radioGroupClassName: 'sm:flex-col',
2654
+ })] })] }), rateChildren: renderRate({
2655
+ rate,
2656
+ depositeName,
2657
+ isShowDepositeName,
2658
+ isUnavailableByTerm,
2659
+ }), resultChildren: jsxs("div", { children: [renderDepositResults(finalSum, income), renderButtonsSection(buttons, { isVertical: true }), renderFootnote(footnote), renderFootnote(bottomFootnote)] }) }));
2660
+ });
2661
+ const renderBusinessDepositParamsForm = (calculatorParams, field) => (jsxs("div", { className: "space-y-s", children: [jsx(Text, { color: "text-secondary-text", font: "font-light", children: "\u041F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B" }), jsxs("div", { className: "flex flex-col gap-y-xl sm:gap-y-s", children: [jsx(Checkbox, { text: "\u041F\u043E\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435", disabled: calculatorParams.isDisabledOperatingDeposit, ...field('isReplenished'), value: calculatorParams.isReplenished }), jsx(Checkbox, { disabled: calculatorParams.isDisabledOperatingDeposit, text: "\u0427\u0430\u0441\u0442\u0438\u0447\u043D\u043E\u0435 \u0441\u043D\u044F\u0442\u0438\u0435", ...field('isPartialWithdrawal'), value: calculatorParams.isPartialWithdrawal })] })] }));
2662
+ const renderDepositResults = (finalSum, income) => (jsxs("div", { className: "flex flex-col gap-2xl sm:gap-0 mb-lg", children: [jsx(CalculatorValueBlock, { title: "\u0414\u043E\u0445\u043E\u0434 \u0432 \u043A\u043E\u043D\u0446\u0435 \u0441\u0440\u043E\u043A\u0430", value: income, postfix: "\u20BD", prefix: "+" }), jsx(CalculatorValueBlock, { title: "\u0421\u0443\u043C\u043C\u0430 \u0432 \u043A\u043E\u043D\u0446\u0435 \u0441\u0440\u043E\u043A\u0430", value: finalSum, postfix: "\u20BD" })] }));
2875
2663
 
2876
- const getItemWidth = (container) => (itemCount) => container.scrollWidth / itemCount;
2877
- const scrollBy = (container) => (itemCount, delta) => {
2878
- container.scrollBy({ left: delta * getItemWidth(container)(itemCount), behavior: 'smooth' });
2664
+ const checkCreditSourceBookRowCommonParams = (props) => checkRowParam$1({
2665
+ ...props,
2666
+ fieldKey: 'isSalaryClient',
2667
+ viewSettingFieldKey: 'isShowSalaryClient',
2668
+ }) &&
2669
+ checkRowParam$1({
2670
+ ...props,
2671
+ fieldKey: 'isStateEmployee',
2672
+ viewSettingFieldKey: 'isShowStateEmployee',
2673
+ }) &&
2674
+ checkRowParam$1({
2675
+ ...props,
2676
+ fieldKey: 'isPensionClient',
2677
+ viewSettingFieldKey: 'isShowPensionClient',
2678
+ }) &&
2679
+ checkRowParam$1({
2680
+ ...props,
2681
+ fieldKey: 'isInsurance',
2682
+ viewSettingFieldKey: 'isShowInsurance',
2683
+ });
2684
+ const checkRowParam$1 = ({ row, userInputParams, paramsViewSettings, fieldKey, viewSettingFieldKey, }) => {
2685
+ const isExclude = paramsViewSettings ? !paramsViewSettings[viewSettingFieldKey] : false;
2686
+ return isExclude || userInputParams[fieldKey] === row[fieldKey];
2879
2687
  };
2880
2688
 
2881
- const scrollToEnd = (container) => {
2882
- container.scrollTo({ left: container.scrollWidth, behavior: 'smooth' });
2883
- };
2689
+ const getCreditCalculatorLimitParams = ({ limitRows, userInputParams, paramsViewSettings, }) => limitRows?.find((row) => checkCreditSourceBookRowCommonParams({
2690
+ row,
2691
+ userInputParams,
2692
+ paramsViewSettings,
2693
+ }));
2884
2694
 
2885
- const scrollToStart = (container) => {
2886
- container.scrollTo({ left: 0, behavior: 'smooth' });
2887
- };
2695
+ const getCreditCalculatorRateParams = ({ rateRows = [], userInputParams, paramsViewSettings, moneyValue, monthsValue, }) => rateRows?.find((row) => checkCreditSourceBookRowCommonParams({
2696
+ row,
2697
+ userInputParams,
2698
+ paramsViewSettings,
2699
+ }) &&
2700
+ checkRangeByKeys(moneyValue, ['minSum', 'maxSum'], row) &&
2701
+ checkRangeByKeys(monthsValue, ['minMonths', 'maxMonths'], row)) || DEFAULT_CREDIT_CALCULATOR_PARAMS;
2888
2702
 
2889
- const useSwipeListScroll = ({ itemCount, onVisibleIndicesChange, }) => {
2890
- const containerRef = useRef(null);
2891
- const [visibleIndices, setVisibleIndices] = useState([]);
2892
- const handleVisibleIndicesChange = useCallback((indices) => {
2893
- setVisibleIndices((prevIndices) => areArraysEqual(prevIndices, indices) ? prevIndices : indices);
2894
- onVisibleIndicesChange && onVisibleIndicesChange(indices);
2895
- }, [itemCount]);
2896
- const [minIndex, maxIndex] = visibleIndices.length
2897
- ? [minBy()(visibleIndices), maxBy()(visibleIndices)]
2898
- : [-1, -1];
2899
- const canScrollLeft = minIndex > 0;
2900
- const canScrollRight = maxIndex < itemCount - 1;
2901
- const scrollLeft = useCallback(() => {
2902
- if (!containerRef.current) {
2903
- return;
2904
- }
2905
- if (canScrollLeft) {
2906
- scrollBy(containerRef.current)(itemCount, -1);
2703
+ /**
2704
+ * Формулы расчёта аннуитетных и дифференцированных платежей:
2705
+ * https://finuslugi.ru/potrebitelskie_kredity/stat_annuitetnye_i_differentsirovannye_platezhi
2706
+ */
2707
+ const getMonthlyPayment = (rate, params) => {
2708
+ const { isAnnuity, moneyValue, monthsValue, moneyExtra } = params;
2709
+ if (rate === 0) {
2710
+ return 0;
2711
+ }
2712
+ else if (isAnnuity) {
2713
+ const i = rate / MONTHLY_INTEREST_RATE; // Используется значение ставки в процентах
2714
+ if (moneyExtra) {
2715
+ return (moneyValue + moneyExtra) * (i + i / (Math.pow(1 + i, monthsValue) - 1));
2907
2716
  }
2908
2717
  else {
2909
- scrollToEnd(containerRef.current);
2910
- }
2911
- }, [itemCount, canScrollLeft]);
2912
- const scrollRight = useCallback(() => {
2913
- if (!containerRef.current) {
2914
- return;
2718
+ return moneyValue * (i + i / (Math.pow(1 + i, monthsValue) - 1));
2915
2719
  }
2916
- if (canScrollRight) {
2917
- scrollBy(containerRef.current)(itemCount, 1);
2720
+ }
2721
+ else {
2722
+ return Math.round(moneyValue / monthsValue) + moneyValue * (rate / (MONTHS_IN_YEAR * 100)); // Используется значение ставки (rate) в процентах, делённое на сто.
2723
+ }
2724
+ };
2725
+
2726
+ const getCreditCalculatorParams = (userInputParams, calcSource, paramsViewSettings) => {
2727
+ const { moneyValue, monthsValue } = userInputParams;
2728
+ const rateRows = calcSource?.rateRows || [DEFAULT_CREDIT_CALCULATOR_PARAMS];
2729
+ const limitRows = calcSource?.limitRows || [];
2730
+ const creditCalculatorSourceBookLimitParams = getCreditCalculatorLimitParams({
2731
+ limitRows,
2732
+ userInputParams,
2733
+ paramsViewSettings,
2734
+ });
2735
+ const { minSum, maxSum } = getLimitRangeByKeys(['minSum', 'maxSum'], creditCalculatorSourceBookLimitParams, rateRows);
2736
+ const { minMonths, maxMonths } = getLimitRangeByKeys(['minMonths', 'maxMonths'], creditCalculatorSourceBookLimitParams, rateRows);
2737
+ const creditCalculatorRateParams = getCreditCalculatorRateParams({
2738
+ rateRows,
2739
+ userInputParams,
2740
+ paramsViewSettings,
2741
+ moneyValue,
2742
+ monthsValue,
2743
+ });
2744
+ const { rate } = creditCalculatorRateParams;
2745
+ const monthlyPayment = getMonthlyPayment(rate, userInputParams);
2746
+ return {
2747
+ ...creditCalculatorRateParams,
2748
+ ...userInputParams,
2749
+ minSum,
2750
+ maxSum,
2751
+ minMonths,
2752
+ maxMonths,
2753
+ moneyValue,
2754
+ monthsValue,
2755
+ monthlyPayment,
2756
+ };
2757
+ };
2758
+
2759
+ const STEP_MONTHS = 1;
2760
+ const renderMonthsInput = ({ min = 0, max = 0, availableMonths, ...rest }) => (jsx(InputRange, { items: [`От ${monthText(min)}`, `До ${monthText(max)}`], step: STEP_MONTHS, ...(availableMonths?.length ? { list: availableMonths } : { min, max }), ...rest }));
2761
+ const monthText = (month) => `${month} ${Number(month) % 10 === 1 ? 'месяца' : 'месяцев'}`;
2762
+
2763
+ const renderPaymentType = (props) => renderBinaryRadioButtonGroup({
2764
+ ...props,
2765
+ radioGroupClassName: 'sm:flex-col',
2766
+ items: [
2767
+ { id: 'annuity', text: 'Аннуитетный' },
2768
+ { id: 'differential', text: 'Дифференцированный' },
2769
+ ],
2770
+ });
2771
+
2772
+ const CreditCalculatorForm = JSX(({ className, calcSource, buttons, footnote, paramsViewSettings, defaultParams }) => {
2773
+ const [userInputParams, { field }] = useForm({
2774
+ moneyValue: getMoneyDefaultValue(defaultParams, CREDIT_DEFAULT_SUM),
2775
+ monthsValue: getMonthsDefaultValue(defaultParams, DEFAULT_MONTHS),
2776
+ isAnnuity: Boolean(defaultParams?.isAnnuity),
2777
+ isInsurance: Boolean(defaultParams?.isEnableInsurance),
2778
+ isSalaryClient: Boolean(defaultParams?.isEnableSalaryClient),
2779
+ isPensionClient: Boolean(defaultParams?.isEnablePensionClient),
2780
+ isStateEmployee: Boolean(defaultParams?.isEnableStateEmployee),
2781
+ });
2782
+ const calculatorParams = getCreditCalculatorParams(userInputParams, calcSource, paramsViewSettings);
2783
+ const paymentTypeElement = renderPaymentType({
2784
+ title: '',
2785
+ mobileTitle: 'Тип платежа',
2786
+ orientation: 'vertical',
2787
+ ...field('isAnnuity'),
2788
+ });
2789
+ const { onChange: setMoneyValue } = field('moneyValue');
2790
+ const { onChange: setMonthsValue } = field('monthsValue');
2791
+ useEffect(() => {
2792
+ setMoneyValue &&
2793
+ setMoneyValue(clamp(userInputParams.moneyValue, calculatorParams.minSum, calculatorParams.maxSum));
2794
+ }, [calculatorParams.minSum, calculatorParams.maxSum]);
2795
+ useEffect(() => {
2796
+ setMonthsValue &&
2797
+ setMonthsValue(clamp(userInputParams.monthsValue, calculatorParams.minMonths, calculatorParams.maxMonths));
2798
+ }, [calculatorParams.minMonths, calculatorParams.maxMonths]);
2799
+ return (jsx(CalculatorFormLayout, { className: className, inputChildren: jsxs("div", { children: [renderWantedSumInput({
2800
+ title: 'Сумма кредита, ₽',
2801
+ step: 500,
2802
+ min: calculatorParams.minSum,
2803
+ max: calculatorParams.maxSum,
2804
+ ...field('moneyValue'),
2805
+ value: calculatorParams.moneyValue,
2806
+ }), renderMonthsInput({
2807
+ title: 'Срок кредита, месяцев',
2808
+ min: calculatorParams.minMonths,
2809
+ max: calculatorParams.maxMonths,
2810
+ ...field('monthsValue'),
2811
+ value: calculatorParams.monthsValue,
2812
+ }), renderCreditParamsForm$1({ ...calculatorParams, ...paramsViewSettings }, field), jsx("div", { className: "sm:hidden", children: paymentTypeElement })] }), rateChildren: renderRate({
2813
+ rate: calculatorParams.rate,
2814
+ }), resultChildren: jsxs("div", { children: [jsx(CalculatorValueBlock, { title: "\u0415\u0436\u0435\u043C\u0435\u0441\u044F\u0447\u043D\u044B\u0439 \u043F\u043B\u0430\u0442\u0451\u0436", value: calculatorParams.monthlyPayment, postfix: "\u20BD" }), jsx("div", { className: "hidden sm:block mb-lg", children: paymentTypeElement }), renderButtonsSection(buttons, { isVertical: true }), renderFootnote(footnote)] }) }));
2815
+ });
2816
+ const renderCreditParamsForm$1 = (params, field) => (jsxs("div", { className: "flex flex-col gap-y-2xl", children: [params.isShowSalaryClient ? (jsx(Checkbox, { text: `Получаю зарплату на счёт\nв Россельхозбанке`, ...field('isSalaryClient'), value: params.isSalaryClient, className: "whitespace-pre-line sm:whitespace-normal" })) : null, params.isShowStateEmployee ? (jsx(Checkbox, { text: "\u0420\u0430\u0431\u043E\u0442\u0430\u044E \u0432 \u0431\u044E\u0434\u0436\u0435\u0442\u043D\u043E\u0439 \u043E\u0440\u0433\u0430\u043D\u0438\u0437\u0430\u0446\u0438\u0438", ...field('isStateEmployee'), value: params.isStateEmployee })) : null, params.isShowPensionClient ? (jsx(Checkbox, { text: "\u041F\u043E\u043B\u0443\u0447\u0430\u044E \u043F\u0435\u043D\u0441\u0438\u044E \u043D\u0430 \u043A\u0430\u0440\u0442\u0443 \u0420\u043E\u0441\u0441\u0435\u043B\u044C\u0445\u043E\u0437\u0431\u0430\u043D\u043A\u0430", ...field('isPensionClient'), value: params.isPensionClient })) : null, params.isShowInsurance ? (jsx(Checkbox, { text: "\u041A\u043E\u043C\u043F\u043B\u0435\u043A\u0441\u043D\u0430\u044F \u0441\u0442\u0440\u0430\u0445\u043E\u0432\u0430\u044F \u0437\u0430\u0449\u0438\u0442\u0430", ...field('isInsurance'), value: params.isInsurance })) : null] }));
2817
+
2818
+ const arrayByRange = (start, stop, step = 1) => Array.from({ length: Math.abs(stop - start) / step + 1 }, (value, index) => stop >= start ? start + index * step : start - index * step);
2819
+
2820
+ const mergeIntervals = (intervals) => {
2821
+ const result = [...intervals];
2822
+ result.sort(([a], [c]) => a - c);
2823
+ let i = 1;
2824
+ while (i < result.length) {
2825
+ const [a, b] = result[i - 1];
2826
+ const [c, d] = result[i];
2827
+ if (b >= c) {
2828
+ result.splice(i - 1, 2, [a, Math.max(b, d)]);
2918
2829
  }
2919
2830
  else {
2920
- scrollToStart(containerRef.current);
2921
- }
2922
- }, [itemCount, canScrollRight]);
2923
- const scrollTo = useCallback((idx, options = {
2924
- behavior: 'smooth',
2925
- block: 'nearest',
2926
- inline: 'center',
2927
- }) => {
2928
- if (containerRef?.current) {
2929
- containerRef.current.children[idx]?.scrollIntoView(options);
2831
+ i++;
2930
2832
  }
2931
- }, [containerRef]);
2932
- return [
2933
- containerRef,
2934
- {
2935
- scrollLeft,
2936
- scrollRight,
2937
- scrollTo,
2938
- canScrollLeft,
2939
- canScrollRight,
2940
- visibleIndicesRange: [minIndex, maxIndex],
2941
- handleVisibleIndicesChange,
2942
- },
2943
- ];
2833
+ }
2834
+ return result;
2944
2835
  };
2945
2836
 
2946
- const scrollButtonStyle$1 = 'absolute top-[calc(50%-24px)] z-40';
2947
- const blurBaseStyle = 'absolute top-0 bottom-0 z-40 hidden @lg:block w-20 pointer-events-none';
2948
- const CarouselScrollButtons = JSX(({ scroll, activeIndex, itemCount, arrowsPadded, hasEdgeBlocks, onScrollLeft, onScrollRight, }) => {
2949
- const { canScrollLeft, canScrollRight, scrollLeft, scrollRight } = scroll;
2950
- const canMoveLeft = hasEdgeBlocks ? activeIndex > 0 : canScrollLeft;
2951
- const canMoveRight = hasEdgeBlocks ? activeIndex < itemCount - 1 : canScrollRight;
2952
- const handleScrollLeft = useCallback(() => {
2953
- if (!hasEdgeBlocks) {
2954
- scrollLeft();
2955
- }
2956
- else {
2957
- canScrollLeft && activeIndex !== itemCount - 1 && scrollLeft();
2958
- onScrollLeft();
2959
- }
2960
- }, [scrollLeft, onScrollLeft, hasEdgeBlocks, canScrollLeft, activeIndex, itemCount]);
2961
- const handleScrollRight = useCallback(() => {
2962
- if (!hasEdgeBlocks) {
2963
- scrollRight();
2964
- }
2965
- else {
2966
- canScrollRight && activeIndex !== 0 && scrollRight();
2967
- onScrollRight();
2968
- }
2969
- }, [scrollRight, onScrollRight, hasEdgeBlocks, canScrollLeft, activeIndex, itemCount]);
2970
- return (jsxs("div", { className: "m:hidden block", children: [renderBlur(scroll), canMoveLeft ? (jsx(ScrollLeftButton, { className: style(scrollButtonStyle$1, arrowsPadded ? 'left-1 m:left-8' : 'left-0'), onClick: handleScrollLeft })) : null, canMoveRight ? (jsx(ScrollRightButton, { className: style(scrollButtonStyle$1, arrowsPadded ? 'right-1 m:right-8' : 'right-0'), onClick: handleScrollRight })) : null] }));
2837
+ const getDepositCalculatorParams = (userInputParams, calcSource, paramsViewSettings) => {
2838
+ const { moneyValue, monthsValue } = userInputParams;
2839
+ const defaultMonth = calcSource?.rateRows?.[0]
2840
+ ? getMiddleMonth(calcSource.rateRows[0].minMonths, calcSource.rateRows[0].maxMonths)
2841
+ : 0;
2842
+ const isExcludeMonthlyInterestPayment = !paramsViewSettings?.isShowMonthlyInterestPayment;
2843
+ const isExcludeRemoteRegistration = !paramsViewSettings?.isShowRemoteRegistration;
2844
+ const depositCalculatorSourceBookParams = calcSource?.rateRows?.find((row) => checkRowByParam({
2845
+ row,
2846
+ userInputParams,
2847
+ paramKey: 'isMonthlyInterestPayment',
2848
+ isExclude: isExcludeMonthlyInterestPayment,
2849
+ }) &&
2850
+ checkRowByParam({
2851
+ row,
2852
+ userInputParams,
2853
+ paramKey: 'isRemoteRegistration',
2854
+ isExclude: isExcludeRemoteRegistration,
2855
+ }) &&
2856
+ checkRangeByKeys(moneyValue, ['minSum', 'maxSum'], row) &&
2857
+ checkRangeByKeys(monthsValue || defaultMonth, ['minMonths', 'maxMonths'], row)) || DEFAULT_DEPOSIT_CALCULATOR_PARAMS;
2858
+ const { rate } = depositCalculatorSourceBookParams;
2859
+ const income = getDepositIncome(rate, userInputParams);
2860
+ const finalSum = moneyValue + income;
2861
+ const sourceBookRows = calcSource?.rateRows || [DEFAULT_DEPOSIT_CALCULATOR_PARAMS];
2862
+ return {
2863
+ ...depositCalculatorSourceBookParams,
2864
+ ...getRangeByKeys(['minSum', 'maxSum'], sourceBookRows),
2865
+ ...getRangeByKeys(['minMonths', 'maxMonths'], sourceBookRows),
2866
+ availableMonths: mergeIntervals(sourceBookRows.map((row) => [row['minMonths'], row['maxMonths']])).flatMap(([start, end]) => arrayByRange(start, end)),
2867
+ ...userInputParams,
2868
+ defaultMonth,
2869
+ income,
2870
+ finalSum,
2871
+ };
2872
+ };
2873
+ const getDepositIncome = (rate, userInputParams) => {
2874
+ const { isMonthlyInterestPayment, moneyValue, monthsValue } = userInputParams;
2875
+ if (isMonthlyInterestPayment) {
2876
+ return Math.round(moneyValue * Math.pow((1200 + rate) / 1200, monthsValue) - moneyValue);
2877
+ }
2878
+ else {
2879
+ /*
2880
+ Приблизительный расчёт, не учитывающий разницу в количестве дней в месяцах. Месяц принят за 30 дней, год за 365 дней.
2881
+ */
2882
+ return Math.round((moneyValue * rate * 30 * monthsValue) / 365 / 100);
2883
+ }
2884
+ };
2885
+ const checkRowByParam = ({ row, userInputParams, paramKey, isExclude }) => isExclude || userInputParams[paramKey] === row[paramKey];
2886
+ const getMiddleMonth = (minMonths, maxMonths) => minMonths + Math.round((maxMonths - minMonths) / 2);
2887
+
2888
+ const renderRegistrationType = (props) => renderBinaryRadioButtonGroup({
2889
+ ...props,
2890
+ items: [
2891
+ { id: 'remote', text: 'Дистанционно' },
2892
+ { id: 'office', text: 'Офис Банка' },
2893
+ ],
2894
+ });
2895
+
2896
+ const DepositCalculatorForm = JSX((props) => {
2897
+ const { className, calcSource, buttons, footnote, paramsViewSettings, defaultParams } = props;
2898
+ const [userInputParams, { field }] = useForm({
2899
+ moneyValue: getMoneyDefaultValue(defaultParams, DEPOSIT_DEFAULT_SUM),
2900
+ monthsValue: getMonthsDefaultValue(defaultParams),
2901
+ isMonthlyInterestPayment: Boolean(defaultParams?.isMonthlyInterestPayment),
2902
+ isRemoteRegistration: Boolean(defaultParams?.isRemoteRegistration),
2903
+ });
2904
+ const calculatorParams = getDepositCalculatorParams(userInputParams, calcSource, paramsViewSettings);
2905
+ const { defaultMonth, rate, income, finalSum } = calculatorParams;
2906
+ const { onChange: setMonthsValue } = field('monthsValue');
2907
+ const { onChange: setMoneyValue } = field('moneyValue');
2908
+ useEffect(() => {
2909
+ !userInputParams.monthsValue && setMonthsValue && setMonthsValue(defaultMonth);
2910
+ }, [defaultMonth]);
2911
+ useEffect(() => {
2912
+ setMoneyValue &&
2913
+ setMoneyValue(clamp(userInputParams.moneyValue, calculatorParams.minSum, calculatorParams.maxSum));
2914
+ }, [calculatorParams.minSum, calculatorParams.maxSum]);
2915
+ return (jsx(CalculatorFormLayout, { className: className, inputChildren: jsxs("div", { children: [renderWantedSumInput({
2916
+ title: 'Сумма, ₽',
2917
+ min: calculatorParams.minSum,
2918
+ max: calculatorParams.maxSum,
2919
+ ...field('moneyValue'),
2920
+ value: calculatorParams.moneyValue,
2921
+ }), renderMonthsInput({
2922
+ title: 'Срок, месяцев',
2923
+ min: calculatorParams.minMonths,
2924
+ max: calculatorParams.maxMonths,
2925
+ availableMonths: calculatorParams.availableMonths,
2926
+ ...field('monthsValue'),
2927
+ value: calculatorParams.monthsValue,
2928
+ }), renderDepositParamsForm({ ...calculatorParams, ...paramsViewSettings }, field)] }), rateChildren: renderRate({
2929
+ rate,
2930
+ }), resultChildren: jsxs("div", { children: [jsx(CalculatorValueBlock, { title: "\u0414\u043E\u0445\u043E\u0434 \u0432 \u043A\u043E\u043D\u0446\u0435 \u0441\u0440\u043E\u043A\u0430", value: income, postfix: "\u20BD", prefix: "+" }), jsx(CalculatorValueBlock, { title: "\u0421\u0443\u043C\u043C\u0430 \u0432 \u043A\u043E\u043D\u0446\u0435 \u0441\u0440\u043E\u043A\u0430", value: finalSum, postfix: "\u20BD" }), renderButtonsSection(buttons, { isVertical: true }), renderFootnote(footnote)] }) }));
2931
+ });
2932
+ const renderDepositParamsForm = (params, field) => (jsxs("div", { className: "flex flex-col gap-y-2xl", children: [params.isShowMonthlyInterestPayment
2933
+ ? renderInterestPayment({
2934
+ title: 'Выплата процентов',
2935
+ ...field('isMonthlyInterestPayment'),
2936
+ value: params.isMonthlyInterestPayment,
2937
+ })
2938
+ : null, params.isShowRemoteRegistration
2939
+ ? renderRegistrationType({
2940
+ title: 'Оформление',
2941
+ ...field('isRemoteRegistration'),
2942
+ value: params.isRemoteRegistration,
2943
+ })
2944
+ : null] }));
2945
+
2946
+ const checkRefinancingSourceBookRowCommonParams = (props) => checkRowParam({
2947
+ ...props,
2948
+ fieldKey: 'isSalaryClient',
2949
+ viewSettingFieldKey: 'isShowSalaryClient',
2950
+ }) &&
2951
+ checkRowParam({
2952
+ ...props,
2953
+ fieldKey: 'isInsurance',
2954
+ viewSettingFieldKey: 'isShowInsurance',
2955
+ });
2956
+ const checkRowParam = ({ row, userInputParams, paramsViewSettings, fieldKey, viewSettingFieldKey, }) => {
2957
+ const isExclude = paramsViewSettings ? !paramsViewSettings[viewSettingFieldKey] : false;
2958
+ return isExclude || userInputParams[fieldKey] === row[fieldKey];
2959
+ };
2960
+
2961
+ const getRefinancingCalculatorLimitParams = ({ limitRows, userInputParams, paramsViewSettings, }) => limitRows?.find((row) => checkRefinancingSourceBookRowCommonParams({
2962
+ row,
2963
+ userInputParams,
2964
+ paramsViewSettings,
2965
+ }));
2966
+
2967
+ const getRefinancingCalculatorRateParams = ({ rateRows = [], userInputParams, paramsViewSettings, moneyValue, monthsValue, oldMonthlyPayment, moneyExtra, }) => rateRows?.find((row) => checkRefinancingSourceBookRowCommonParams({
2968
+ row,
2969
+ userInputParams,
2970
+ paramsViewSettings,
2971
+ }) &&
2972
+ checkRangeByKeys(moneyValue + moneyExtra, ['minSum', 'maxSum'], row) &&
2973
+ checkRangeByKeys(oldMonthlyPayment, ['minSumOld', 'maxSumOld'], row) &&
2974
+ checkRangeByKeys(moneyExtra, ['minSumExtra', 'maxSumExtra'], row) &&
2975
+ checkRangeByKeys(monthsValue, ['minMonths', 'maxMonths'], row)) || DEFAULT_REFINANCING_CALCULATOR_PARAMS;
2976
+
2977
+ const getRefinancingCalculatorParams = (userInputParams, calcSource, paramsViewSettings) => {
2978
+ const { moneyValue, monthsValue, oldMonthlyPayment, moneyExtra } = userInputParams;
2979
+ const rateRows = calcSource?.rateRows || [DEFAULT_REFINANCING_CALCULATOR_PARAMS];
2980
+ const limitRows = calcSource?.limitRows || [];
2981
+ const sourceBookLimitParams = getRefinancingCalculatorLimitParams({
2982
+ limitRows,
2983
+ userInputParams,
2984
+ paramsViewSettings,
2985
+ });
2986
+ const { minSum, maxSum } = getLimitRangeByKeys(['minSum', 'maxSum'], sourceBookLimitParams, rateRows);
2987
+ const { minMonths, maxMonths } = getLimitRangeByKeys(['minMonths', 'maxMonths'], sourceBookLimitParams, rateRows);
2988
+ const refinancingCalculatorSourceBookRateParams = getRefinancingCalculatorRateParams({
2989
+ rateRows,
2990
+ userInputParams,
2991
+ paramsViewSettings,
2992
+ moneyValue,
2993
+ monthsValue,
2994
+ oldMonthlyPayment,
2995
+ moneyExtra,
2996
+ });
2997
+ const { rate } = refinancingCalculatorSourceBookRateParams;
2998
+ const monthlyPayment = getMonthlyPayment(rate, userInputParams);
2999
+ const benefit = userInputParams.oldMonthlyPayment - monthlyPayment;
3000
+ return {
3001
+ ...refinancingCalculatorSourceBookRateParams,
3002
+ ...userInputParams,
3003
+ minSum,
3004
+ maxSum,
3005
+ minMonths,
3006
+ maxMonths,
3007
+ moneyValue,
3008
+ monthsValue,
3009
+ oldMonthlyPayment,
3010
+ moneyExtra,
3011
+ monthlyPayment,
3012
+ benefit,
3013
+ };
3014
+ };
3015
+
3016
+ const RefinancingCalculatorForm = JSX(({ className, calcSource, buttons, footnote, paramsViewSettings }) => {
3017
+ const [userInputParams, { field }] = useForm({
3018
+ isAnnuity: true,
3019
+ isInsurance: true,
3020
+ isSalaryClient: false,
3021
+ isExtraMoney: false,
3022
+ moneyValue: REFINANCING_DEFAULT_SUM,
3023
+ oldMonthlyPayment: REFINANCING_OLD_DEFAULT_SUM,
3024
+ monthsValue: DEFAULT_REFINANCING_MONTHS,
3025
+ moneyExtra: REFINANCING_OLD_DEFAULT_SUM,
3026
+ });
3027
+ const calculatorParams = getRefinancingCalculatorParams(userInputParams, calcSource, paramsViewSettings);
3028
+ const paymentTypeElement = renderPaymentType({
3029
+ title: '',
3030
+ mobileTitle: 'Тип платежа',
3031
+ orientation: 'vertical',
3032
+ ...field('isAnnuity'),
3033
+ });
3034
+ return (jsx(CalculatorFormLayout, { className: className, inputChildren: jsxs("div", { children: [renderWantedSumInput({
3035
+ title: 'Остаток долга в других банках, ₽ ',
3036
+ step: 500,
3037
+ min: calculatorParams.minSum,
3038
+ max: calculatorParams.maxSum - calculatorParams.maxSumExtra,
3039
+ ...field('moneyValue'),
3040
+ value: calculatorParams.moneyValue,
3041
+ }), renderWantedSumInput({
3042
+ title: 'Ежемесячный платёж в других банках, ₽',
3043
+ step: 500,
3044
+ min: calculatorParams.minSumOld,
3045
+ max: calculatorParams.maxSumOld,
3046
+ ...field('oldMonthlyPayment'),
3047
+ value: calculatorParams.oldMonthlyPayment,
3048
+ }), renderMonthsInput({
3049
+ title: 'Новый срок кредита, месяцев',
3050
+ min: calculatorParams.minMonths,
3051
+ max: calculatorParams.maxMonths,
3052
+ ...field('monthsValue'),
3053
+ value: calculatorParams.monthsValue,
3054
+ }), renderCreditParamsForm({ ...calculatorParams, ...paramsViewSettings }, field), calculatorParams.isExtraMoney
3055
+ ? renderWantedSumInput({
3056
+ title: 'Дополнительная сумма, ₽ ',
3057
+ step: 500,
3058
+ min: calculatorParams.minSumExtra,
3059
+ max: calculatorParams.maxSumExtra,
3060
+ ...field('moneyExtra'),
3061
+ value: calculatorParams.moneyExtra,
3062
+ })
3063
+ : null, jsx("div", { className: "sm:hidden pt-1 pb-4 border-b border-main-divider", children: paymentTypeElement })] }), rateChildren: renderRate({
3064
+ rate: calculatorParams.rate,
3065
+ fractionDigits: 1,
3066
+ }), resultChildren: jsxs("div", { children: [jsx(CalculatorValueBlock, { title: "\u0415\u0436\u0435\u043C\u0435\u0441\u044F\u0447\u043D\u044B\u0439 \u043F\u043B\u0430\u0442\u0451\u0436", value: calculatorParams.monthlyPayment, postfix: "\u20BD" }), calculatorParams.benefit > 0 ? (jsx(CalculatorValueBlock, { title: 'Выгода в месяц', value: calculatorParams.benefit, postfix: "\u20BD" })) : null, jsx("div", { className: "hidden sm:block mb-lg", children: paymentTypeElement }), renderButtonsSection(buttons, { isVertical: true }), renderFootnote(footnote)] }) }));
2971
3067
  });
2972
- const renderBlur = (scroll) => (jsxs("div", { children: [scroll.canScrollLeft ? (jsx("div", { className: style(blurBaseStyle, 'left-0 bg-opacity-from-white group-data-secondary:bg-none') })) : null, scroll.canScrollRight ? (jsx("div", { className: style(blurBaseStyle, 'right-0 bg-opacity-to-white group-data-secondary:bg-none') })) : null] }));
3068
+ const renderCreditParamsForm = (params, field) => (jsxs("div", { className: "space-y-lg sm:space-y-s", children: [params.isShowSalaryClient ? (jsx(Checkbox, { text: "\u041F\u043E\u043B\u0443\u0447\u0430\u044E \u0437\u0430\u0440\u043F\u043B\u0430\u0442\u0443 \u043D\u0430 \u0441\u0447\u0451\u0442 \u0432 \u0420\u043E\u0441\u0441\u0435\u043B\u044C\u0445\u043E\u0437\u0431\u0430\u043D\u043A\u0435", ...field('isSalaryClient'), value: params.isSalaryClient })) : null, params.isShowInsurance ? (jsx(Checkbox, { text: "\u041A\u043E\u043C\u043F\u043B\u0435\u043A\u0441\u043D\u0430\u044F \u0441\u0442\u0440\u0430\u0445\u043E\u0432\u0430\u044F \u0437\u0430\u0449\u0438\u0442\u0430", ...field('isInsurance'), value: params.isInsurance })) : null, jsx(Checkbox, { text: "\u041F\u043E\u043B\u0443\u0447\u0438\u0442\u044C \u0434\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u0443\u044E \u0441\u0443\u043C\u043C\u0443", ...field('isExtraMoney'), value: params.isExtraMoney })] }));
2973
3069
 
2974
- const ITEMS_GAP = 16;
3070
+ const EmbeddableCalcBlocks = {
3071
+ CreditCalculatorForm,
3072
+ DepositCalculatorForm,
3073
+ BusinessDepositCalculatorForm,
3074
+ BonusCalculatorForm,
3075
+ RefinancingCalculatorForm,
3076
+ };
2975
3077
 
2976
- const getBorderStyle = (showBorder = false) => showBorder
2977
- ? 'border border-solid border-gray group-data-secondary:border-white/50 group-data-gray:border-main-divider'
2978
- : '';
3078
+ const renderCalculatorBlock = (block, i) => {
3079
+ const type = block?.calcType;
3080
+ if (!type || !(type in EmbeddableCalcBlocks)) {
3081
+ return null;
3082
+ }
3083
+ const EmbeddedBlock = EmbeddableCalcBlocks[type];
3084
+ return (jsx("section", { className: "box-border relative bg-white flex shrink-0 self-start basis-full", role: "listitem", children: jsxs("div", { className: "box-border p-4xl w-full space-y-xl", children: [block?.title ? jsx(Heading, { className: "text-center", title: block.title }) : null, jsx(EmbeddedBlock, { ...block })] }) }, String(i)));
3085
+ };
2979
3086
 
2980
- const renderCardHighlight = ({ className, text = 'ХИТ' } = {}) => (jsxs("div", { className: style('w-14 h-14 bg-white flex flex-col items-center justify-center rounded-md shadow-main-black', className), children: [jsx(Icon, { iconVersion: "black", name: "HeartIcon", width: "24", height: "24" }), jsx(Paragraph, { size: "text-s", font: "font-normal", align: "text-center", color: "text-primary-text", children: text })] }));
3087
+ const Calculator = UniBlock(({ className = '', tabAlign, calculatorTabs = [], ...rest }) => {
3088
+ const tabsNav = calculatorTabs.map((_) => _.nav || {});
3089
+ const calculatorBlocks = calculatorTabs.map(({ calculatorBlock }) => calculatorBlock || {});
3090
+ const [slideIndexShift, setSlideIndexShift] = useState(0);
3091
+ const handleSlideIndexShift = useCallback((_) => setSlideIndexShift(_), []);
3092
+ const COLUMNS_FOR_SCROLL = 3;
3093
+ const [activeIndex, controls] = useCarouselControls({
3094
+ itemCount: calculatorTabs?.length,
3095
+ visibleItemCount: COLUMNS_FOR_SCROLL,
3096
+ });
3097
+ return (jsxs(BlockWrapper, { className: style('box-border overflow-hidden relative', className), defaultPadding: "p-0", ...rest, children: [jsxs("div", { className: "bg-white", children: [tabsNav.length > 1
3098
+ ? renderTabs$1({
3099
+ activeIndex,
3100
+ tabsNav,
3101
+ tabAlign,
3102
+ slideIndexShift,
3103
+ onClick: handleSlideIndexShift,
3104
+ })
3105
+ : null, renderArrows({
3106
+ ...controls,
3107
+ btnClass: ['left-3', 'right-3'],
3108
+ className: 'top-10',
3109
+ })] }), jsx("div", { className: "flex", style: { transform: `translateX(-${slideIndexShift}00%)` }, role: "list", children: calculatorBlocks?.length ? calculatorBlocks.map(renderCalculatorBlock) : null })] }));
3110
+ });
3111
+ function renderNavButton$3({ tab, i, isActive, onClick }) {
3112
+ const btnClassName = isActive
3113
+ ? 'w-96 p-m border-none bg-primary-main text-white'
3114
+ : `w-96 p-s ease-in duration-300 bg-white text-primary-text`;
3115
+ const btnTitleClassName = isActive ? 'text-xl mb-xs' : `text-l mb-2xs`;
3116
+ const btnDescClassName = isActive
3117
+ ? 'text-l font-light text-white'
3118
+ : `text-m font-light text-secondary-text`;
3119
+ const iconClassName = isActive ? '' : `text-primary-main`;
3120
+ const iconVersion = tab?.icon?.iconVersion;
3121
+ const activeIconStyle = isActive ? 'white' : 'normal';
3122
+ return (jsxs("button", { type: "button", onClick: onClick, "aria-label": tab?.title, className: style('box-border relative border border-gray cursor-pointer text-left border-b-0 flex space-x-m min-h-20', btnClassName), children: [jsx(Img, { className: style('h-6 w-6 min-w-6 min-h-6', iconClassName), image: {
3123
+ ...tab?.icon,
3124
+ iconVersion: iconVersion ?? activeIconStyle,
3125
+ }, width: "24", height: "24" }), jsxs("div", { className: "border-0", children: [jsx("div", { className: btnTitleClassName, children: tab?.title }), jsx("div", { className: btnDescClassName, children: tab.description })] })] }, String(i)));
3126
+ }
3127
+ function renderTabs$1({ activeIndex, tabsNav, tabAlign, slideIndexShift, onClick }) {
3128
+ const CARD_SHIFT = 388;
3129
+ const alignStyle = tabAlign ? AlignJustifyStyle[tabAlign] : '';
3130
+ return (jsxs("div", { children: [jsx("div", { style: { transform: `translateX(-${activeIndex * CARD_SHIFT}px)` }, className: "flex flex-col justify-end items-center h-32 pt-4xl box-border duration-1000", children: jsx("div", { className: style('w-full flex flex-nowrap items-end gap-xs', alignStyle), children: tabsNav.map((tab, i) => renderNavButton$3({
3131
+ tab,
3132
+ i,
3133
+ isActive: Boolean(i === slideIndexShift),
3134
+ onClick: () => onClick(i),
3135
+ })) }) }), jsx("div", { className: "w-full h-2 bg-primary-main" })] }));
3136
+ }
2981
3137
 
2982
- const cardBaseStyle = 'relative h-full';
2983
- const CARD_SIZE_MAP = {
2984
- small: 'sm:min-w-64',
2985
- normal: 'sm:min-w-96',
2986
- big: 'sm:min-w-[515px]',
2987
- };
2988
- const renderCarouselCards = ({ noCardBorder, activeIndex, cardSize = 'normal', ...rest }) => {
2989
- const { blocks: childBlocks } = rest.block || {};
2990
- const someCardsHighlighted = childBlocks?.some(isCardHighlighted);
2991
- return renderChildren({
2992
- ...rest,
2993
- renderProps: (block, options, blockIndex) => (jsxs("div", { className: cardWrapperStyle(block, { someCardsHighlighted }), children: [renderBlock$1(block, options, {
2994
- className: cardStyle(block, {
2995
- someCardsHighlighted,
2996
- showBorder: !noCardBorder,
2997
- cardSize,
2998
- }),
2999
- blockIndex,
3000
- isActive: blockIndex === activeIndex,
3001
- }), isCardHighlighted(block)
3002
- ? renderCardHighlight({ className: 'absolute top-0 -right-4 z-40' })
3003
- : null] }, String(blockIndex))),
3138
+ const NumberInput = JSX((props) => {
3139
+ const { onChange, min, max, defaultValue = '0', fractionDigits = 0, ...inputProps } = props;
3140
+ const normalizedProps = useNormalizedInput(props, {
3141
+ normalize: (_ = defaultValue) => min && max ? String(clamp(Number(sanitizeNumberStr(_)), min, max)) : sanitizeNumberStr(_),
3142
+ format: toLocalNumberFormat(fractionDigits),
3004
3143
  });
3005
- };
3006
- const cardWrapperStyle = (_, { someCardsHighlighted }) => style(cardBaseStyle, someCardsHighlighted && (isCardHighlighted(_) ? 'pt-3xl' : 'pt-3xl sm:pt-16'));
3007
- const cardStyle = (_, { someCardsHighlighted, showBorder, cardSize = 'normal', }) => style(cardBaseStyle, 'min-w-80', CARD_SIZE_MAP[cardSize], // TODO fix для RecommendationCard
3008
- someCardsHighlighted && (isCardHighlighted(_) ? 'pt-3xl sm:pt-16' : 'pt-3xl'), getBorderStyle(showBorder), showBorder && 'h-full');
3009
- const isCardHighlighted = (_) => _?.content && 'isHighlighting' in _.content && _.content.isHighlighting;
3144
+ const handleChange = useCallback((_) => onChange && onChange(sanitizeNumberStr(_)), [onChange]);
3145
+ return jsx(Input, { onChange: handleChange, ...inputProps, ...normalizedProps });
3146
+ });
3147
+ const sanitizeNumberStr = (_ = '') => _.replaceAll(',', '.')
3148
+ .replace(/[^\d.]/g, '')
3149
+ .replace(/([^.]*\.[^.]*)\./, '$1');
3010
3150
 
3011
- const VISIBLE_ITEM_COUNT = 1;
3151
+ const CurrencyInput = JSX((props) => (jsx(NumberInput, { ...props, fractionDigits: 2, children: jsx("div", { className: "absolute right-4 bottom-4", children: jsx(Text, { size: "text-xl", font: "font-light", children: "\u20BD" }) }) })));
3012
3152
 
3013
- const Carousel = UniBlock(({ className, itemCount, visibleItemCount = VISIBLE_ITEM_COUNT, noCardBorder, isAlignCenter, arrowsPadded = true, isGap = true, isEmbedded = false, cardSize = 'normal', ...rest }) => {
3014
- const { blocks: childBlocks = [] } = rest.block || {};
3015
- const count = itemCount || childBlocks?.length;
3016
- const [activeIndex, { inc: increaseActiveIndex, dec: decreaseActiveIndex }] = useCarouselControls({
3017
- itemCount: count,
3018
- visibleItemCount,
3019
- }, Math.floor(visibleItemCount / 2));
3020
- const [containerRef, scroll] = useSwipeListScroll({ itemCount: count });
3021
- const [dotsActiveIndex] = scroll.visibleIndicesRange;
3022
- // TODO Remove
3023
- const isBankHistoryCarousel = childBlocks && childBlocks[0]?.type === 'CarouselBankHistoryCard';
3024
- return (jsxs(BlockWrapper, { className: style(isEmbedded ? '@container-normal' : '', className), defaultPadding: "p-6xl", ...rest, children: [jsxs("div", { className: style(isAlignCenter ? 'flex justify-center' : '', 'relative max-w-full'), children: [jsx(SwipeListContainer, { containerRef: containerRef, visibleItemCount: visibleItemCount, snapAlign: "snap-start", gap: isGap ? ITEMS_GAP : undefined, onVisibleIndicesChange: scroll.handleVisibleIndicesChange, children: renderCarouselCards({ noCardBorder, activeIndex, cardSize, ...rest }) }), jsx(CarouselScrollButtons, { scroll: scroll, arrowsPadded: arrowsPadded, onScrollLeft: decreaseActiveIndex, onScrollRight: increaseActiveIndex, hasEdgeBlocks: isBankHistoryCarousel, activeIndex: activeIndex, itemCount: count })] }), jsx(SwipeListControlDots, { className: "@5xl:hidden", containerRef: containerRef, activeIndex: dotsActiveIndex, dotCount: count, listType: "horizontal-list" })] }));
3025
- }, {
3026
- childrenTypes: [
3027
- 'CarouselCard',
3028
- 'CarouselBankHistoryCard',
3029
- 'CarouselCatalogCard',
3030
- 'CarouselRecommendationCard',
3031
- 'CarouselTariffsCard',
3032
- 'RichTextBlock',
3033
- 'ProductBlock',
3034
- ],
3035
- childSchema: () => ({
3036
- isHighlighting: {
3037
- title: 'Выделить карточку',
3038
- type: 'boolean',
3039
- },
3040
- }),
3153
+ const DEFAULT_TITLE = 'Укажите сумму перевода';
3154
+ const DEFAULT_LABEL = 'Сумма перевода';
3155
+ const DEFAULT_PLACEHOLDER = '1500';
3156
+ const MAX_AMOUNT = 300000;
3157
+ const MIN_AMOUNT = 1;
3158
+ const FORM_URL_MAP = {
3159
+ private: 'https://old.rshb.ru/p2p/',
3160
+ business: 'https://old.rshb.ru/smallbusiness/p2p/',
3161
+ };
3162
+ const INITIAL_FORM_STATE = {
3163
+ amount: 1500,
3164
+ };
3165
+ const CardTransfer = JSX(({ className = '', title = DEFAULT_TITLE, label = DEFAULT_LABEL, placeholder = DEFAULT_PLACEHOLDER, button, formType = 'private', ...rest }) => {
3166
+ const [{ amount }, { field }] = useForm(INITIAL_FORM_STATE);
3167
+ const [isDisabled, setDisabled] = useState(false);
3168
+ useEffect(() => {
3169
+ setDisabled(amount <= 0);
3170
+ }, [amount]);
3171
+ return (jsxs(BlockWrapper, { className: style('flex flex-col items-center', className), defaultPadding: "p-6xl", ...rest, children: [title ? (jsx(Heading, { className: "pb-lg xl:pb-4xl self-start xl:self-center", headingType: "h3", title: title })) : null, jsxs("form", { className: "w-full xl:max-w-[468px]", method: "POST", action: FORM_URL_MAP[formType], children: [jsx(CurrencyInput, { valid: !isDisabled, placeholder: placeholder, label: label, max: MAX_AMOUNT, min: MIN_AMOUNT, ...field('amount', { parse: Number, format: String }) }), jsx("input", { type: "hidden", name: "amount", value: amount }), button?.text ? (jsx(Button, { className: "w-full mt-lg", type: "submit", disabled: isDisabled, children: button.text })) : null] })] }));
3041
3172
  });
3042
3173
 
3043
3174
  const CarouselBankHistoryCard = JSX(({ className, title, description, blockIndex = 0, isActive, ...rest }) => (jsx(BlockWrapper, { className: style('relative flex h-[485px]', isActive ? 'w-[440px] z-10' : 'w-[340px]', className), defaultPadding: "p-0", ...rest, children: jsx("div", { className: style('basis-full self-center', getBackColor(blockIndex)), children: isActive ? (jsx(Headline, { headlineVersion: "M", title: title, description: description, padding: "p-3xl", align: "text-left", className: "h-96 text-white shadow-dark-blue" })) : (jsx("div", { className: "h-[300px] flex justify-center items-center font-mohave text-title-large", children: jsx("span", { className: getTextColor(blockIndex), children: title }) })) }) })));
@@ -3322,8 +3453,6 @@
3322
3453
  return (jsx(DefaultFoldButton, { icon: icons[Number(isUnfolded)], label: labels[Number(isUnfolded)], onClick: onToggle }));
3323
3454
  };
3324
3455
 
3325
- const NotFound = JSX(({ title, description }) => (jsxs("div", { className: "bg-white p-6xl py-20 flex gap-s flex-col @xl:items-center", children: [jsx(Img, { image: { icon: 'MagnifierIcon', iconVersion: 'normal' }, width: "120", height: "120" }), jsx(Headline, { title: title, description: description, headlineVersion: "S", isEmbedded: true, align: AlignStyles })] })));
3326
-
3327
3456
  const ICONS$1 = ['ArrowDownIcon', 'ArrowUpIcon'];
3328
3457
  const resultsGridStyle = 'space-y-px lg:space-y-0 lg:grid lg:grid-cols-12 gap-2xs border-box';
3329
3458
  const BaseFiltrationInner = JSX(({ label = '', blocksToRender = [], topItemsCount = 0, block, notFound, ...rest }) => {
@@ -3335,95 +3464,6 @@
3335
3464
  }, renderFoldButton: ({ isUnfolded, onToggle }) => (jsx(DefaultFoldButton, { className: style(topItemsCount < 1 ? 'hidden' : ''), disabled: !hiddenBlocks?.length, label: label, icon: ICONS$1[Number(isUnfolded)], dataTheme: rest.options?.page?.colorPalette, onClick: onToggle })) })] })] }));
3336
3465
  });
3337
3466
 
3338
- const SlideCheckboxGroup = JSX(({ value, onChange, items = [], multiple }) => {
3339
- const handleChange = (newValue) => {
3340
- if (!onChange) {
3341
- return;
3342
- }
3343
- const hasAlready = value?.includes(newValue);
3344
- if (multiple) {
3345
- onChange(hasAlready ? (value || []).filter((v) => v !== newValue) : [...(value || []), newValue]);
3346
- }
3347
- else {
3348
- onChange(hasAlready ? [] : [newValue]);
3349
- }
3350
- };
3351
- return items.map((item) => (jsx(SlideCheckbox, { text: item?.title, value: [value].flatMap((_) => _).includes(item.value), onChange: () => handleChange(item.value), type: multiple ? 'checkbox' : 'radio' }, item.value)));
3352
- });
3353
-
3354
- const renderFiltrationForm$1 = ({ field, reset, filtrationSchema, resetButton, children, }) => (jsxs("form", { className: "flex flex-col @4xl:flex-row justify-between bg-white p-4xl gap-s @4xl:gap-0", children: [jsxs("div", { className: "flex flex-col @4xl:flex-row gap-5xl", children: [children, Object.entries(filtrationSchema).map(renderFormItem(field))] }), resetButton ? (jsx("div", { children: jsx(Button, { type: "reset", className: "w-full @4xl:w-72 @4xl:ml-s", onClick: reset, children: resetButton?.text }) })) : null] }));
3355
- const renderFormItem = (field) => ([key, item]) => {
3356
- const type = item?.type;
3357
- switch (type) {
3358
- case 'array':
3359
- return renderCheckboxes(key, field)(item);
3360
- default:
3361
- return null;
3362
- }
3363
- };
3364
- const renderCheckboxes = (key, field) => (item) => {
3365
- const options = item?.items?.enum?.map((e, i) => ({ value: e, title: item?.items?.enumNames?.[i] || '' })) ||
3366
- [];
3367
- return (jsxs("div", { children: [item?.title ? (jsx("div", { className: "mb-m", children: jsx(Text, { size: "text-m", font: "font-light", color: "text-secondary-text", children: item.title }) })) : null, jsx(SlideCheckboxGroup, { items: options, multiple: !item?.single, ...field(key) })] }, key));
3368
- };
3369
-
3370
- function zip(firstCol, ...restCols) {
3371
- return firstCol.map((firstEl, rowIndex) => [firstEl, ...restCols.map((col) => col[rowIndex])]);
3372
- }
3373
-
3374
- const isEmpty = (v) => v === null ||
3375
- v === undefined ||
3376
- v === '' ||
3377
- (Array.isArray(v) && v.length === 0) ||
3378
- (typeof v === 'object' && Object.keys(v).length === 0);
3379
-
3380
- const isSingularFieldMatch = (key, pattern) => (target) => isEmpty(pattern[key]) || isEmpty(target[key]) || pattern[key] === target[key];
3381
- const isMultipleFieldMatch = (key, pattern) => (target) => isEmpty(pattern[key]) ||
3382
- isEmpty(target[key]) ||
3383
- pattern[key]?.every((_) => target[key]?.includes(_));
3384
- const isNumberFieldMatch = (key, pattern) => (target) => isEmpty(pattern[key]) ||
3385
- isEmpty(target[key]) ||
3386
- naturalNumber(pattern[key]) <= naturalNumber(target[key]);
3387
- const isMultipleNumberFieldMatch = (key, pattern) => (target) => isEmpty(pattern[key]) || isEmpty(target[key]) || target[key]?.includes(pattern[key]);
3388
- const isValueInRange = (key, pattern) => (target) => isEmpty(pattern[key]) ||
3389
- isEmpty(target[key]) ||
3390
- (naturalNumber(pattern[key]?.min) >= naturalNumber(target[key]?.min) &&
3391
- naturalNumber(pattern[key]?.min) <= naturalNumber(target[key]?.max));
3392
- const targetMatchPattern = (pattern, target) => Object.entries(pattern)
3393
- .map(([key, subPattern]) => {
3394
- if (typeof subPattern === 'number') {
3395
- return Array.isArray(target[key])
3396
- ? isMultipleNumberFieldMatch(key, pattern)
3397
- : isNumberFieldMatch(key, pattern);
3398
- }
3399
- if (typeof subPattern === 'object' && 'min' in subPattern && 'max' in subPattern) {
3400
- return isValueInRange(key, pattern);
3401
- }
3402
- return Array.isArray(subPattern)
3403
- ? isMultipleFieldMatch(key, pattern)
3404
- : isSingularFieldMatch(key, pattern);
3405
- })
3406
- .every((predicate) => predicate(target));
3407
- const naturalNumber = (n) => Number(n || 0);
3408
-
3409
- const filterBlocks = (blocksWithProps, filtrationState) => (normalize = identity) => blocksWithProps
3410
- ?.filter(([, content]) => targetMatchPattern(filtrationState, normalize(content) || {}))
3411
- .map(([block]) => block);
3412
-
3413
- const useFiltrationForm = (initialFormState, { block, page, normalize = identity, }) => {
3414
- const blocks = block?.blocks || [];
3415
- const [filtrationState, { field, reset, update: replaceFiltrationState }] = useForm(initialFormState);
3416
- const blocksWithProps = useMemo(() => zip(blocks, blocks.map((b) => resolveBlockProps(b.content, page?.fallback))), [blocks, page?.fallback]);
3417
- const blocksToRender = filtrationState === initialFormState
3418
- ? blocks
3419
- : filterBlocks(blocksWithProps, filtrationState)(normalize);
3420
- return [
3421
- filtrationState,
3422
- { blocksToRender, blocksWithProps },
3423
- { field, reset, update: replaceFiltrationState },
3424
- ];
3425
- };
3426
-
3427
3467
  const isRateRow = (rateRow) => {
3428
3468
  return typeof rateRow?.minMonths === 'number' && typeof rateRow?.maxMonths === 'number';
3429
3469
  };
@@ -3447,11 +3487,10 @@
3447
3487
  });
3448
3488
  const rateRows = blocksWithProps.map(([, _]) => _?.paramsSource?.rateRows);
3449
3489
  const months = getMonths(rateRows);
3450
- const Inputs = (jsxs("div", { children: [jsx(CurrencyInput, { label: sumInput?.title, min: DEFAULT_MIN_SUM, max: DEFAULT_MAX_SUM, ...field('sum', {
3451
- parse: (min) => ({ min: Number(min), max: DEFAULT_MAX_SUM }),
3452
- format: ({ min }) => String(min),
3453
- }) }), jsx(InputRange, { title: periodInput?.title, items: ['От 3 мес', 'До 5 лет'], list: months, ...field('period') })] }));
3454
- return (jsxs(BlockWrapper, { className: style('space-y-2xs', className), defaultPadding: "p-0", version: "transparent", ...rest, children: [renderFiltrationForm$1({ field, reset, filtrationSchema, resetButton, children: Inputs }), jsx(BaseFiltrationInner, { blocksToRender: blocksToRender, ...rest })] }));
3490
+ return (jsxs(BlockWrapper, { className: style('space-y-2xs', className), defaultPadding: "p-0", version: "transparent", ...rest, children: [jsx(FiltrationForm, { filtrationSchema: filtrationSchema, resetButton: resetButton, field: field, reset: reset, children: jsxs("div", { className: "@4xl:max-w-xs", children: [jsx(CurrencyInput, { label: sumInput?.title, min: DEFAULT_MIN_SUM, max: DEFAULT_MAX_SUM, ...field('sum', {
3491
+ parse: (min) => ({ min: Number(min), max: DEFAULT_MAX_SUM }),
3492
+ format: ({ min }) => String(min),
3493
+ }) }), jsx(InputRange, { title: periodInput?.title, items: ['От 3 мес', 'До 5 лет'], list: months, ...field('period') })] }) }), jsx(BaseFiltrationInner, { blocksToRender: blocksToRender, ...rest })] }));
3455
3494
  }, {
3456
3495
  childrenTypes: ['ProductBlock'],
3457
3496
  childSchema: (content) => ({
@@ -4429,8 +4468,6 @@
4429
4468
  return (jsx("button", { type: "button", onClick: onClick, "aria-label": title, className: `box-border px-m py-s text-m font-light ${btnClassName}`, children: title }, String(i)));
4430
4469
  }
4431
4470
 
4432
- const joinList = (sep) => (list) => list.reduce((acc, el, i) => (acc.length ? acc.concat({ ...sep, key: `sep-${i}` }, el) : [el]), []);
4433
-
4434
4471
  const investmentCellColorMap = {
4435
4472
  yellow: 'bg-yellow',
4436
4473
  green: 'bg-green',
@@ -4637,7 +4674,7 @@
4637
4674
  blocks: rest.block?.blocks,
4638
4675
  onFiltrationStateChange: handleFiltrationStateChange,
4639
4676
  });
4640
- return (jsxs(BlockWrapper, { className: style('space-y-2xs', className), defaultPadding: "p-0", version: "transparent", ...rest, children: [jsx(TabsControl, { ...tabsControlProps }), renderFiltrationForm$1({ field, reset, filtrationSchema, resetButton }), jsx(BaseFiltrationInner, { blocksToRender: blocksToRender, ...rest })] }));
4677
+ return (jsxs(BlockWrapper, { className: style('space-y-2xs', className), defaultPadding: "p-0", version: "transparent", ...rest, children: [jsx(TabsControl, { ...tabsControlProps }), jsx(FiltrationForm, { filtrationSchema: filtrationSchema, resetButton: resetButton, field: field, reset: reset }), jsx(BaseFiltrationInner, { blocksToRender: blocksToRender, ...rest })] }));
4641
4678
  }, {
4642
4679
  childrenTypes: ['ProductBlock'],
4643
4680
  childSchema: (content) => content?.filtrationSchema || {},
@@ -5788,6 +5825,7 @@
5788
5825
  AdTile,
5789
5826
  ApplicationForm,
5790
5827
  ApplicationFormCustom,
5828
+ BankAccountFiltration,
5791
5829
  BenefitsBlock,
5792
5830
  BonusBenefitsBlock,
5793
5831
  ButtonsBlock,
@@ -6049,7 +6087,7 @@
6049
6087
  slots: () => [HEADER_SLOT, FOOTER_SLOT, STICKY_FOOTER_SLOT],
6050
6088
  });
6051
6089
 
6052
- const packageVersion = "0.14.245";
6090
+ const packageVersion = "0.14.247";
6053
6091
 
6054
6092
  exports.Blocks = Blocks;
6055
6093
  exports.ContentPage = ContentPage;