@snabcentr/client-ui 4.21.0 → 4.24.0

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 (104) hide show
  1. package/alerts/confirm-alert/sc-confirm-alert.component.d.ts +22 -0
  2. package/alerts/index.d.ts +1 -0
  3. package/cart/add-or-editing-cart-item-dialog/sc-add-or-editing-cart-item-dialog.component.d.ts +1 -1
  4. package/cart/sc-cart-add-products-from-csv-dialog/sc-cart-add-products-from-csv-dialog.component.d.ts +3 -3
  5. package/catalog/input-quantity/sc-input-quantity.component.d.ts +17 -1
  6. package/esm2022/alerts/confirm-alert/sc-confirm-alert.component.mjs +29 -0
  7. package/esm2022/alerts/index.mjs +2 -0
  8. package/esm2022/cart/add-or-editing-cart-item-dialog/sc-add-or-editing-cart-item-dialog.component.mjs +4 -3
  9. package/esm2022/cart/sc-cart-add-products-from-csv-dialog/sc-cart-add-products-from-csv-dialog.component.mjs +5 -16
  10. package/esm2022/catalog/input-quantity/sc-input-quantity.component.mjs +24 -4
  11. package/esm2022/feedback/feedback-form/sc-feedback-form.component.mjs +21 -11
  12. package/esm2022/helpers/sc-api-error-helper.mjs +25 -0
  13. package/esm2022/helpers/sc-file-helper.mjs +29 -0
  14. package/esm2022/order/draft/helpers/sc-draft-list-helper.mjs +83 -0
  15. package/esm2022/order/draft/models/sc-i-draft-list-item.mjs +2 -0
  16. package/esm2022/order/draft/profstroy/actualization-panel/sc-profstroy-actualization-panel.component.mjs +73 -0
  17. package/esm2022/order/draft/profstroy/badge/sc-profstroy-badge.component.mjs +21 -0
  18. package/esm2022/order/draft/profstroy/badges/sc-profstroy-draft-badges.component.mjs +31 -0
  19. package/esm2022/order/draft/profstroy/create-dialog/sc-profstroy-draft-create-dialog.component.mjs +139 -0
  20. package/esm2022/order/draft/profstroy/enums/sc-profstroy-draft-state.mjs +32 -0
  21. package/esm2022/order/draft/profstroy/helpers/sc-profstroy-draft-helper.mjs +94 -0
  22. package/esm2022/order/draft/profstroy/index.mjs +8 -0
  23. package/esm2022/order/draft/profstroy/models/sc-i-profstroy-draft-state-view.mjs +2 -0
  24. package/esm2022/order/draft/sc-draft.component.mjs +148 -14
  25. package/esm2022/order/draft/services/sc-draft-expiration-clock.service.mjs +42 -0
  26. package/esm2022/order/draft/services/sc-draft-list-updates.service.mjs +288 -0
  27. package/esm2022/order/index.mjs +6 -1
  28. package/esm2022/order/new-order-items/sc-new-order-items.component.mjs +11 -3
  29. package/esm2022/order/order-form/sc-order-form.component.mjs +4 -2
  30. package/esm2022/order/order-item/sc-order-item.component.mjs +37 -5
  31. package/esm2022/order/order-items-list/sc-order-items-list.component.mjs +254 -47
  32. package/esm2022/order/order-items-list-by-stock/sc-order-items-list-by-stock.component.mjs +1 -1
  33. package/esm2022/order/order-items-list-with-actions/sc-order-items-list-with-actions.component.mjs +15 -3
  34. package/esm2022/pipes/sc-format-date.mjs +1 -1
  35. package/esm2022/providers/cleanup-recaptcha-artifacts.mjs +30 -0
  36. package/esm2022/providers/index.mjs +2 -1
  37. package/esm2022/providers/sc-date-formatter.mjs +1 -1
  38. package/esm2022/public-api.mjs +2 -2
  39. package/esm2022/samples/sc-sample.module.mjs +10 -3
  40. package/esm2022/services/index.mjs +1 -2
  41. package/feedback/feedback-form/sc-feedback-form.component.d.ts +1 -1
  42. package/fesm2022/snabcentr-client-ui.mjs +1399 -477
  43. package/fesm2022/snabcentr-client-ui.mjs.map +1 -1
  44. package/helpers/sc-api-error-helper.d.ts +18 -0
  45. package/helpers/sc-file-helper.d.ts +13 -0
  46. package/order/draft/helpers/sc-draft-list-helper.d.ts +51 -0
  47. package/order/draft/models/sc-i-draft-list-item.d.ts +14 -0
  48. package/order/draft/profstroy/actualization-panel/sc-profstroy-actualization-panel.component.d.ts +58 -0
  49. package/order/draft/profstroy/badge/sc-profstroy-badge.component.d.ts +12 -0
  50. package/order/draft/profstroy/badges/sc-profstroy-draft-badges.component.d.ts +21 -0
  51. package/order/draft/profstroy/create-dialog/sc-profstroy-draft-create-dialog.component.d.ts +65 -0
  52. package/order/draft/profstroy/enums/sc-profstroy-draft-state.d.ts +29 -0
  53. package/order/draft/profstroy/helpers/sc-profstroy-draft-helper.d.ts +33 -0
  54. package/order/draft/profstroy/index.d.ts +7 -0
  55. package/order/draft/profstroy/models/sc-i-profstroy-draft-state-view.d.ts +17 -0
  56. package/order/draft/sc-draft.component.d.ts +73 -1
  57. package/order/draft/services/sc-draft-expiration-clock.service.d.ts +29 -0
  58. package/order/draft/services/sc-draft-list-updates.service.d.ts +158 -0
  59. package/order/index.d.ts +5 -0
  60. package/order/new-order-items/sc-new-order-items.component.d.ts +9 -1
  61. package/order/order-form/sc-order-form.component.d.ts +1 -0
  62. package/order/order-item/sc-order-item.component.d.ts +25 -1
  63. package/order/order-items-list/sc-order-items-list.component.d.ts +132 -9
  64. package/order/order-items-list-with-actions/sc-order-items-list-with-actions.component.d.ts +13 -1
  65. package/package.json +3 -2
  66. package/pipes/sc-format-date.d.ts +1 -1
  67. package/providers/cleanup-recaptcha-artifacts.d.ts +7 -0
  68. package/providers/index.d.ts +1 -0
  69. package/providers/sc-date-formatter.d.ts +1 -1
  70. package/public-api.d.ts +1 -1
  71. package/release_notes.tmp +2 -10
  72. package/samples/sc-sample.module.d.ts +2 -1
  73. package/services/index.d.ts +0 -1
  74. package/styles/tailwind/tailwind.scss +43 -40
  75. package/esm2022/services/sc-scripts.service.mjs +0 -106
  76. package/esm2022/smart-captcha/index.mjs +0 -5
  77. package/esm2022/smart-captcha/interfaces/index.mjs +0 -6
  78. package/esm2022/smart-captcha/interfaces/sc-smart-captcha-api.mjs +0 -2
  79. package/esm2022/smart-captcha/interfaces/sc-smart-captcha-event.mjs +0 -2
  80. package/esm2022/smart-captcha/interfaces/sc-smart-captcha-language.mjs +0 -2
  81. package/esm2022/smart-captcha/interfaces/sc-smart-captcha-render-params.mjs +0 -2
  82. package/esm2022/smart-captcha/interfaces/sc-smart-captcha-settings.mjs +0 -2
  83. package/esm2022/smart-captcha/interfaces/sc-smart-captcha-window.mjs +0 -2
  84. package/esm2022/smart-captcha/sc-smart-captcha.component.mjs +0 -167
  85. package/esm2022/smart-captcha/services/index.mjs +0 -2
  86. package/esm2022/smart-captcha/services/sc-smart-captcha-loader.service.mjs +0 -87
  87. package/esm2022/smart-captcha/tokens/index.mjs +0 -3
  88. package/esm2022/smart-captcha/tokens/sc-smart-captcha-load-timeout.token.mjs +0 -8
  89. package/esm2022/smart-captcha/tokens/sc-smart-captcha-settings.token.mjs +0 -6
  90. package/services/sc-scripts.service.d.ts +0 -51
  91. package/smart-captcha/index.d.ts +0 -4
  92. package/smart-captcha/interfaces/index.d.ts +0 -5
  93. package/smart-captcha/interfaces/sc-smart-captcha-api.d.ts +0 -36
  94. package/smart-captcha/interfaces/sc-smart-captcha-event.d.ts +0 -4
  95. package/smart-captcha/interfaces/sc-smart-captcha-language.d.ts +0 -4
  96. package/smart-captcha/interfaces/sc-smart-captcha-render-params.d.ts +0 -16
  97. package/smart-captcha/interfaces/sc-smart-captcha-settings.d.ts +0 -16
  98. package/smart-captcha/interfaces/sc-smart-captcha-window.d.ts +0 -14
  99. package/smart-captcha/sc-smart-captcha.component.d.ts +0 -92
  100. package/smart-captcha/services/index.d.ts +0 -1
  101. package/smart-captcha/services/sc-smart-captcha-loader.service.d.ts +0 -50
  102. package/smart-captcha/tokens/index.d.ts +0 -2
  103. package/smart-captcha/tokens/sc-smart-captcha-load-timeout.token.d.ts +0 -5
  104. package/smart-captcha/tokens/sc-smart-captcha-settings.token.d.ts +0 -6
@@ -1,22 +1,20 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, Injectable, NgZone, RendererFactory2, signal, EventEmitter, ChangeDetectorRef, Directive, Input, Output, untracked, Pipe, ElementRef, HostListener, Renderer2, ContentChildren, NgModule, input, computed, Component, ChangeDetectionStrategy, Inject, HostBinding, model, output, SkipSelf, DestroyRef, effect, ContentChild, ViewChild, viewChild, Optional, contentChild, TemplateRef, forwardRef, ViewEncapsulation, Injector, viewChildren, afterNextRender } from '@angular/core';
2
+ import { InjectionToken, inject, Injectable, NgZone, signal, EventEmitter, ChangeDetectorRef, Directive, Input, Output, untracked, Pipe, ElementRef, HostListener, Renderer2, ContentChildren, NgModule, input, computed, Component, ChangeDetectionStrategy, Inject, HostBinding, model, output, SkipSelf, DestroyRef, effect, ContentChild, ViewChild, viewChild, Optional, contentChild, TemplateRef, forwardRef, ViewEncapsulation, Injector, viewChildren, afterNextRender } from '@angular/core';
3
3
  import * as i1 from '@snabcentr/client-core';
4
- import { ScContactsService, ScUserService, ScLocationsService, IS_BROWSER, ScAuthService, SEARCH_TERM, ScUnitsHelper, ScImageHelper, SC_PATH_IMAGE_NOT_FOUND, ScImage, ScSuggestionAddressQuality, ScPhoneService, ScUserMetrikaService, ScUserMetrikaGoalsEnum, IS_RUNNING_ON_TERMINAL, ScVCardService, ScVerificationService, ScISuggestionType, SC_MIN_LENGTH_SEARCH_TERM, ScConvertersService, ScNumberHelper, ScOpfList, ScReferencesService, ScContragentService, ScBannerService, ScMediaImageTransformerPipe, ScCartService, ScUploadedFile, loadingPlaceholder, SC_ORDER_LOADER, ScMimeTypes, ScCatalogService, SC_URLS, ScWarehouseService, SEARCH_TERM_PROVIDERS, ScPaginationService, SC_NEXT_PAGE_PAGINATION_CLICK, SC_PRODUCT_PAGINATION_OPTIONS, ScCatalogFilterService, ScIdOrSlugPipe, SC_CATEGORY_INFO, ScConfiguratorService, IS_SERVER, RESPONSE, ScIconTypesEnum, ScDocumentInfoTypesEnum, ScDeliveryAddressService, ScOrdersService, ScUIService, SC_USER_INFO as SC_USER_INFO$1, ScPaymentType, ScDeliveryType, TERMINAL_PROVIDERS, ScOrderDraftsService, ScRouteKeys, ScSeoService, scOrderIsLoaded, SC_PAGE_QUERY_PARAM, ScFrequentlyAskedQuestionsService, SchemaOrgFactory, scGetPageQueryParamChanges, ScNewsService, SC_COMPANY_INFO, ScFeedbackService } from '@snabcentr/client-core';
4
+ import { ScContactsService, ScUserService, ScLocationsService, ScAuthService, SEARCH_TERM, ScUnitsHelper, ScImageHelper, SC_PATH_IMAGE_NOT_FOUND, ScImage, ScSuggestionAddressQuality, ScPhoneService, ScUserMetrikaService, ScUserMetrikaGoalsEnum, IS_RUNNING_ON_TERMINAL, ScVCardService, ScVerificationService, ScISuggestionType, SC_MIN_LENGTH_SEARCH_TERM, ScConvertersService, ScNumberHelper, ScOpfList, ScReferencesService, ScContragentService, ScBannerService, ScMediaImageTransformerPipe, ScUploadedFile, ScCartService, loadingPlaceholder, SC_ORDER_LOADER, ScMimeTypes, ScCatalogService, SC_URLS, ScWarehouseService, SEARCH_TERM_PROVIDERS, ScPaginationService, SC_NEXT_PAGE_PAGINATION_CLICK, SC_PRODUCT_PAGINATION_OPTIONS, ScCatalogFilterService, ScIdOrSlugPipe, SC_CATEGORY_INFO, ScConfiguratorService, IS_SERVER, RESPONSE, ScIconTypesEnum, ScDocumentInfoTypesEnum, ScOrderItemsGroup, ScOrderItem, ScDeliveryAddressService, ScOrdersService, ScUIService, SC_USER_INFO as SC_USER_INFO$1, ScPaymentType, ScDeliveryType, TERMINAL_PROVIDERS, ScOrderDraft, SC_UPDATE_INTERVAL, ScOrderDraftsService, ScOrderDraftActualizationStatus, ScRouteKeys, ScSeoService, scOrderIsLoaded, SC_PAGE_QUERY_PARAM, ScFrequentlyAskedQuestionsService, SchemaOrgFactory, scGetPageQueryParamChanges, ScNewsService, SC_COMPANY_INFO, ScFeedbackService } from '@snabcentr/client-core';
5
5
  import * as i5$1 from 'rxjs';
6
- import { EMPTY, BehaviorSubject, switchMap, of, shareReplay, map, Observable, Subject, filter, tap, catchError, finalize, startWith, share, timer, scan, takeWhile, endWith, distinctUntilChanged, debounceTime, throwError, combineLatest, pairwise, Subscription, merge, skip, from, concatMap, noop, first, retry, fromEvent, animationFrames, take, takeUntil, defer, timeout, ignoreElements } from 'rxjs';
6
+ import { EMPTY, BehaviorSubject, switchMap, of, shareReplay, map, Subject, filter, tap, catchError, finalize, startWith, share, timer, scan, takeWhile, endWith, distinctUntilChanged, debounceTime, throwError, combineLatest, pairwise, Observable, Subscription, merge, skip, from, concatMap, noop, first, retry, defaultIfEmpty, exhaustMap, fromEvent, animationFrames, take, takeUntil } from 'rxjs';
7
7
  import { WA_WINDOW } from '@ng-web-apis/common';
8
8
  import { takeUntilDestroyed, toSignal, outputFromObservable, toObservable } from '@angular/core/rxjs-interop';
9
9
  import * as i4$1 from '@taiga-ui/cdk';
10
- import { TuiDay, TuiTime, TuiValueTransformer, TUI_IS_MOBILE, TuiValidationError, tuiControlValue, tuiIsPresent, tuiMarkControlAsTouchedAndValidate, tuiIsFalsy, TuiAutoFocus, TuiRepeatTimes, TuiSwipeService, TUI_SWIPE_OPTIONS, TuiHovered, TuiDayRange, TuiMonth, tuiPure, tuiProvide, TUI_WINDOW_SIZE, tuiClamp, tuiZonefree, TUI_TRUE_HANDLER } from '@taiga-ui/cdk';
10
+ import { TuiDay, TuiTime, TuiValueTransformer, TUI_IS_MOBILE, TuiValidationError, tuiControlValue, tuiIsPresent, tuiMarkControlAsTouchedAndValidate, tuiIsFalsy, TuiAutoFocus, TuiRepeatTimes, TuiSwipeService, TUI_SWIPE_OPTIONS, TuiHovered, TuiDayRange, TuiMonth, tuiPure, tuiProvide, TUI_WINDOW_SIZE, tuiClamp, TUI_TRUE_HANDLER } from '@taiga-ui/cdk';
11
11
  import * as i2$1 from '@taiga-ui/kit';
12
- import { TUI_DATE_VALUE_TRANSFORMER, TuiPreview, TuiAvatar, TuiAccordionItem, TuiElasticContainer, TuiAccordion, TuiFieldErrorPipe, TuiFilterByInputPipe, TuiStringifyContentPipe, TuiDataListWrapper, TuiButtonLoading, TuiSortCountriesPipe, TuiStepper, TuiCarousel, TuiPush, TuiCheckbox, TuiFiles, TuiChip, TuiInputNumber, TuiBadge, TuiTooltip, TuiHighlight, TuiLineClamp, TuiFade, TuiRange, TuiBlock, tuiInputNumberOptionsProvider, TuiDataListWrapperComponent, TuiChevron, TuiSelect, TuiInputSlider, TuiTreeService, TuiTreeItemContent, TUI_TREE_START, TUI_TREE_CONTENT, TUI_TREE_LOADING, TUI_TREE_LOADER, TuiTree, TuiPreviewDialogService, tuiFilesAccepted, TuiInputFiles, TuiInputFilesDirective, TuiFilesComponent, TuiFile, TuiFileRejectedPipe, TUI_CONFIRM, tuiItemsHandlersProvider as tuiItemsHandlersProvider$1, TUI_PAGINATION_TEXTS, TuiAccordionDirective, TuiAccordionItemContent } from '@taiga-ui/kit';
13
- import * as i2$2 from '@angular/common';
14
- import { DOCUMENT, CommonModule, NgIf, AsyncPipe, NgTemplateOutlet, NgFor, NgClass } from '@angular/common';
15
- import { HttpClient, HttpErrorResponse } from '@angular/common/http';
12
+ import { TUI_DATE_VALUE_TRANSFORMER, TuiPreview, TuiAvatar, TuiAccordionItem, TuiElasticContainer, TuiAccordion, TuiFieldErrorPipe, TuiFilterByInputPipe, TuiStringifyContentPipe, TuiDataListWrapper, TuiButtonLoading, TuiSortCountriesPipe, TuiStepper, TuiCarousel, TuiPush, TuiCheckbox, TUI_CONFIRM_WORDS, TuiFiles, TuiChip, TuiInputNumber, TuiBadge, TuiTooltip, TuiHighlight, TuiLineClamp, TuiFade, TuiRange, TuiBlock, tuiInputNumberOptionsProvider, TuiDataListWrapperComponent, TuiChevron, TuiSelect, TuiInputSlider, TuiTreeService, TuiTreeItemContent, TUI_TREE_START, TUI_TREE_CONTENT, TUI_TREE_LOADING, TUI_TREE_LOADER, TuiTree, TuiPreviewDialogService, TUI_CONFIRM, tuiFilesAccepted, TuiInputFiles, TuiInputFilesDirective, TuiFilesComponent, TuiFile, TuiFileRejectedPipe, TUI_INPUT_FILES_OPTIONS, tuiCreateFileFormatValidator, tuiCreateFileSizeValidator, TUI_INPUT_FILE_TEXTS, tuiItemsHandlersProvider as tuiItemsHandlersProvider$1, TUI_PAGINATION_TEXTS, TuiAccordionDirective, TuiAccordionItemContent } from '@taiga-ui/kit';
13
+ import { HttpClient, HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
16
14
  import * as i1$1 from '@angular/forms';
17
- import { FormControl, NgControl, FormGroupDirective, FormGroup, Validators, FormArray, FormsModule, ReactiveFormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
15
+ import { FormControl, NgControl, FormGroupDirective, FormGroup, Validators, FormArray, FormsModule, ReactiveFormsModule } from '@angular/forms';
18
16
  import * as i1$2 from '@taiga-ui/core';
19
- import { TuiButton, TuiDialog, TuiDialogService, TuiLink, TuiFallbackSrcPipe, TuiInitialsPipe, TuiIcon, TuiAppearance, TuiWithAppearance, TuiIcons, TuiWithIcons, TUI_DATA_LIST_HOST, tuiDropdownOptionsProvider, TuiLabel, TuiError, TuiLoader, TuiDataList, TuiNotification, TuiTextfield, TuiTitle, TuiFormatNumberPipe, TuiNumberFormat, TuiHint, TUI_MONTHS, TuiScrollbar, tuiNumberFormatProvider, tuiItemsHandlersProvider, tuiTextfieldOptionsProvider, tuiHintOptionsProvider, TuiHintDriver, TuiHintHover, TuiHintPosition, TuiHintDirective, TuiDataListComponent, TuiOption, TuiAlertService, TuiIconPipe, TUI_BUTTON_OPTIONS, TUI_BUTTON_DEFAULT_OPTIONS, TUI_SPIN_ICONS, TuiSurface, tuiFadeIn } from '@taiga-ui/core';
17
+ import { TuiButton, TuiDialog, TuiDialogService, TuiLink, TuiFallbackSrcPipe, TuiInitialsPipe, TuiIcon, TuiAppearance, TuiWithAppearance, TuiIcons, TuiWithIcons, TUI_DATA_LIST_HOST, tuiDropdownOptionsProvider, TuiLabel, TuiError, TuiLoader, TuiDataList, TuiNotification, TuiTextfield, TuiTitle, TuiFormatNumberPipe, TuiNumberFormat, TuiHint, TUI_MONTHS, TuiScrollbar, tuiNumberFormatProvider, tuiItemsHandlersProvider, tuiTextfieldOptionsProvider, tuiHintOptionsProvider, TuiHintDriver, TuiHintHover, TuiHintPosition, TuiHintDirective, TuiAlertService, TuiDataListComponent, TuiOption, TuiIconPipe, TUI_BUTTON_OPTIONS, TUI_BUTTON_DEFAULT_OPTIONS, TUI_SPIN_ICONS, TuiSurface, tuiFadeIn } from '@taiga-ui/core';
20
18
  import { isValidPhoneNumber } from 'libphonenumber-js/max';
21
19
  import * as i4 from '@taiga-ui/legacy/components/primitive-textfield';
22
20
  import * as i3 from '@taiga-ui/legacy';
@@ -26,6 +24,8 @@ import { isNil, isUndefined, isObject, isArray, isString } from 'lodash-es';
26
24
  import * as i5 from '@taiga-ui/core/components/label';
27
25
  import * as i8 from '@maskito/angular';
28
26
  import { MaskitoDirective } from '@maskito/angular';
27
+ import * as i2$2 from '@angular/common';
28
+ import { DOCUMENT, CommonModule, NgIf, AsyncPipe, NgTemplateOutlet, NgFor, NgClass } from '@angular/common';
29
29
  import * as i2$3 from '@taiga-ui/polymorpheus';
30
30
  import { POLYMORPHEUS_CONTEXT, PolymorpheusComponent, PolymorpheusTemplate, PolymorpheusOutlet, injectContext } from '@taiga-ui/polymorpheus';
31
31
  import * as i2 from 'angularx-qrcode';
@@ -55,6 +55,8 @@ import { Clipboard } from '@angular/cdk/clipboard';
55
55
  import * as i2$5 from '@taiga-ui/experimental/components/expand';
56
56
  import * as i3$3 from '@taiga-ui/kit/components/data-list-wrapper';
57
57
  import { TuiGroup } from '@taiga-ui/core/directives/group';
58
+ import * as i4$3 from 'ng-recaptcha-2';
59
+ import { RecaptchaFormsModule, RecaptchaModule, RecaptchaComponent } from 'ng-recaptcha-2';
58
60
 
59
61
  /**
60
62
  * Поток для закрытия уведомлений помощи.
@@ -354,6 +356,36 @@ const SC_HIDDEN_PRINT_ELEMENTS = new InjectionToken('SC_HIDDEN_PRINT_ELEMENTS',
354
356
  providedIn: 'root',
355
357
  });
356
358
 
359
+ /**
360
+ * Селектор iframe reCAPTCHA.
361
+ */
362
+ const RECAPTCHA_IFRAME_SELECTOR = 'iframe[src*="recaptcha"]';
363
+ /**
364
+ * Селектор хоста reCAPTCHA.
365
+ */
366
+ const RECAPTCHA_HOST_SELECTOR = 're-captcha';
367
+ /**
368
+ * Освобождает ресурсы reCAPTCHA и удаляет осиротевшие iframe после смены маршрута.
369
+ *
370
+ * @param documentReference Объект документа.
371
+ * @param windowReference Объект окна браузера.
372
+ */
373
+ function cleanupRecaptchaArtifacts(documentReference, windowReference) {
374
+ const windowWithRecaptcha = windowReference;
375
+ try {
376
+ windowWithRecaptcha.grecaptcha?.reset();
377
+ }
378
+ catch {
379
+ // Ошибки reCAPTCHA не должны блокировать навигацию.
380
+ }
381
+ documentReference.querySelectorAll(RECAPTCHA_IFRAME_SELECTOR).forEach((iframe) => {
382
+ if (iframe.closest(RECAPTCHA_HOST_SELECTOR)) {
383
+ return;
384
+ }
385
+ iframe.remove();
386
+ });
387
+ }
388
+
357
389
  /**
358
390
  * Сервис по работе с печатью страницы.
359
391
  */
@@ -399,107 +431,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
399
431
  }]
400
432
  }] });
401
433
 
402
- /**
403
- * Сервис для вставки в {@link Document} внешних и дополнительных скриптов.
404
- */
405
- class ScScriptsService {
406
- constructor() {
407
- /**
408
- * Объект {@link Document}, предоставляющий доступ к DOM страницы.
409
- */
410
- this.document = inject(DOCUMENT);
411
- /**
412
- * Объект {@link Renderer2}, предоставляющий доступ к DOM страницы.
413
- */
414
- this.renderer = inject(RendererFactory2).createRenderer(null, null);
415
- /**
416
- * Признак того, что скрипт выполняется в браузере.
417
- */
418
- this.isBrowser = inject(IS_BROWSER);
419
- /**
420
- * {@link NgZone} для вставки скриптов вне зоны Angular.
421
- */
422
- this.ngZone = inject(NgZone);
423
- }
424
- /**
425
- * Вставляет в конец {@link Document.body} скрипты, заданные строками HTML, вне зоны Angular. Из каждой строки берётся первый `<script>`. На сервере ничего не делает.
426
- * Принимает только доверенные строки из конфигурации приложения: строка разбирается как HTML и может выполнить код.
427
- *
428
- * @param scriptStrings Строки HTML скриптов.
429
- */
430
- appendScripts(scriptStrings) {
431
- if (!this.isBrowser) {
432
- return;
433
- }
434
- this.ngZone.runOutsideAngular(() => {
435
- scriptStrings.forEach((scriptString) => {
436
- const script = this.createScript(scriptString);
437
- if (script) {
438
- this.renderer.appendChild(this.document.body, script);
439
- }
440
- });
441
- });
442
- }
443
- /**
444
- * Загружает внешний скрипт: при подписке добавляет его в {@link Document.head}, после загрузки выдаёт элемент скрипта и завершается.
445
- * Если скрипт не загрузился или подписка отменена раньше, удаляет его со страницы; удаление не отменяет загрузку и выполнение уже вставленного скрипта. На сервере завершается без загрузки.
446
- *
447
- * @param url Адрес скрипта.
448
- */
449
- loadScript$(url) {
450
- if (!this.isBrowser) {
451
- return EMPTY;
452
- }
453
- return new Observable((subscriber) => {
454
- const script = this.renderer.createElement('script');
455
- let isLoaded = false;
456
- const unlistenLoad = this.renderer.listen(script, 'load', () => {
457
- isLoaded = true;
458
- subscriber.next(script);
459
- subscriber.complete();
460
- });
461
- const unlistenError = this.renderer.listen(script, 'error', () => {
462
- subscriber.error(new Error(`Failed to load script ${url}`));
463
- });
464
- this.renderer.setProperty(script, 'src', url);
465
- this.renderer.appendChild(this.document.head, script);
466
- return () => {
467
- unlistenLoad();
468
- unlistenError();
469
- if (!isLoaded) {
470
- this.removeScript(script);
471
- }
472
- };
473
- });
474
- }
475
- /**
476
- * Удаляет скрипт со страницы. При подключённых анимациях Angular узел удаляется при ближайшей проверке изменений.
477
- *
478
- * @param script Элемент скрипта.
479
- */
480
- removeScript(script) {
481
- this.renderer.removeChild(script.parentNode, script);
482
- }
483
- /**
484
- * Создаёт {@link HTMLScriptElement} из строки HTML скрипта.
485
- *
486
- * @param scriptString Строка HTML скрипта.
487
- */
488
- createScript(scriptString) {
489
- const range = this.document.createRange();
490
- const fragment = range.createContextualFragment(scriptString);
491
- return fragment.querySelector('script');
492
- }
493
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScScriptsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
494
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScScriptsService, providedIn: 'root' }); }
495
- }
496
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScScriptsService, decorators: [{
497
- type: Injectable,
498
- args: [{
499
- providedIn: 'root',
500
- }]
501
- }] });
502
-
503
434
  /**
504
435
  * Проверяет корректность введенного номера телефона.
505
436
  */
@@ -4460,6 +4391,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4460
4391
  }]
4461
4392
  }] });
4462
4393
 
4394
+ /**
4395
+ * Компонент немодального уведомления с выбором «Да» или «Нет» для событий без действия пользователя, например изменения списка в фоне.
4396
+ * Уведомление завершается выбором пользователя. Действие пользователя подтверждается диалогом TUI_CONFIRM.
4397
+ */
4398
+ class ScConfirmAlertComponent {
4399
+ constructor() {
4400
+ /**
4401
+ * Контекст уведомления: вопрос, подписи и вид кнопки согласия, выбор пользователя.
4402
+ */
4403
+ this.context = injectContext();
4404
+ /**
4405
+ * {@link Observable} подписей кнопок по умолчанию.
4406
+ */
4407
+ this.words$ = inject(TUI_CONFIRM_WORDS);
4408
+ }
4409
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScConfirmAlertComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4410
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ScConfirmAlertComponent, isStandalone: true, selector: "sc-confirm-alert", ngImport: i0, template: "@let words = words$ | async;\n<div class=\"flex flex-col items-start gap-2\">\n <span *polymorpheusOutlet=\"context.data.content as text; context: context\">{{ text }}</span>\n <div class=\"flex gap-2\">\n <button\n tuiButton\n type=\"button\"\n size=\"s\"\n [appearance]=\"context.data.appearance || 'primary'\"\n (click)=\"context.completeWith(true)\"\n >\n {{ context.data.yes || words?.yes }}\n </button>\n <button\n tuiButton\n type=\"button\"\n size=\"s\"\n appearance=\"secondary\"\n (click)=\"context.completeWith(false)\"\n >\n {{ context.data.no || words?.no }}\n </button>\n </div>\n</div>\n", dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "directive", type: PolymorpheusOutlet, selector: "[polymorpheusOutlet]", inputs: ["polymorpheusOutlet", "polymorpheusOutletContext"] }, { kind: "directive", type: TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4411
+ }
4412
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScConfirmAlertComponent, decorators: [{
4413
+ type: Component,
4414
+ args: [{ standalone: true, selector: 'sc-confirm-alert', imports: [AsyncPipe, PolymorpheusOutlet, TuiButton], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let words = words$ | async;\n<div class=\"flex flex-col items-start gap-2\">\n <span *polymorpheusOutlet=\"context.data.content as text; context: context\">{{ text }}</span>\n <div class=\"flex gap-2\">\n <button\n tuiButton\n type=\"button\"\n size=\"s\"\n [appearance]=\"context.data.appearance || 'primary'\"\n (click)=\"context.completeWith(true)\"\n >\n {{ context.data.yes || words?.yes }}\n </button>\n <button\n tuiButton\n type=\"button\"\n size=\"s\"\n appearance=\"secondary\"\n (click)=\"context.completeWith(false)\"\n >\n {{ context.data.no || words?.no }}\n </button>\n </div>\n</div>\n" }]
4415
+ }] });
4416
+
4463
4417
  /**
4464
4418
  * Баннер с прокруткой переданных {@link TemplateRef} элементов, и баннеров локации.
4465
4419
  */
@@ -4640,6 +4594,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4640
4594
  }]
4641
4595
  }] });
4642
4596
 
4597
+ /**
4598
+ * Класс-помощник для работы с файлами, выбранными пользователем.
4599
+ */
4600
+ class ScFileHelper {
4601
+ /**
4602
+ * Читает файл и возвращает его для отправки в API: название и содержимое в виде data-URI. При отписке чтение прерывается.
4603
+ *
4604
+ * @param file Выбранный файл.
4605
+ */
4606
+ static readAsUploadedFile$(file) {
4607
+ return new Observable((observer) => {
4608
+ const reader = new FileReader();
4609
+ reader.addEventListener('load', () => {
4610
+ observer.next(new ScUploadedFile(file.name, reader.result));
4611
+ observer.complete();
4612
+ });
4613
+ reader.addEventListener('error', () => {
4614
+ observer.error(reader.error);
4615
+ });
4616
+ reader.readAsDataURL(file);
4617
+ return () => {
4618
+ reader.abort();
4619
+ };
4620
+ });
4621
+ }
4622
+ }
4623
+
4643
4624
  /**
4644
4625
  * Компонент диалога для добавления товаров в корзину из CSV файла.
4645
4626
  */
@@ -4682,19 +4663,7 @@ class ScCartAddProductsFromCsvDialogComponent {
4682
4663
  /**
4683
4664
  * {@link Observable} запроса на добавление товаров из CSV файла.
4684
4665
  */
4685
- this.submitRequest$ = this.onSubmit$.pipe(map(() => this.control.value), filter(tuiIsPresent), switchMap((file) => new Observable((observer) => {
4686
- const reader = new FileReader();
4687
- reader.addEventListener('load', () => {
4688
- observer.next(new ScUploadedFile(file.name, reader.result));
4689
- });
4690
- reader.addEventListener('error', (e) => {
4691
- observer.error(e);
4692
- });
4693
- reader.readAsDataURL(file);
4694
- return () => {
4695
- reader.abort();
4696
- };
4697
- })), switchMap((file) => this.cartService.addProductsFromCsv$(file).pipe(tap(() => {
4666
+ this.submitRequest$ = this.onSubmit$.pipe(map(() => this.control.value), filter(tuiIsPresent), switchMap((file) => ScFileHelper.readAsUploadedFile$(file)), switchMap((file) => this.cartService.addProductsFromCsv$(file).pipe(tap(() => {
4698
4667
  this.context.$implicit.complete();
4699
4668
  }), startWith(null))));
4700
4669
  /**
@@ -4970,7 +4939,7 @@ class ScAddOrEditingCartItemDialogComponent {
4970
4939
  this.context.$implicit.complete();
4971
4940
  }), catchError((error) => {
4972
4941
  if (error instanceof HttpErrorResponse) {
4973
- const { errors, message } = error.error;
4942
+ const { errors, message } = (error.error ?? {});
4974
4943
  if (errors && isObject(errors)) {
4975
4944
  Object.entries(errors).forEach(([k, v]) => {
4976
4945
  this.formComponent().form.get(k)?.setErrors({ serverResponse: v });
@@ -4982,7 +4951,8 @@ class ScAddOrEditingCartItemDialogComponent {
4982
4951
  this.formComponent().form.updateValueAndValidity();
4983
4952
  this.formComponent().form.markAsDirty();
4984
4953
  }
4985
- return of();
4954
+ // Значение вместо пустого потока снимает загрузку, чтобы форму можно было отправить снова.
4955
+ return of(null);
4986
4956
  }), startWith(loadingPlaceholder))), share());
4987
4957
  /**
4988
4958
  * {@link Observable} изменения состояния загрузки данных.
@@ -5320,6 +5290,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5320
5290
  class ScInputQuantityComponent extends AbstractTuiNullableControl {
5321
5291
  constructor() {
5322
5292
  super(...arguments);
5293
+ /**
5294
+ * Минимальное количество знаков после запятой.
5295
+ */
5296
+ this.minPrecision = 2;
5323
5297
  /**
5324
5298
  * Шаг увеличения количества. Отвечает за проверку кратности ввода.
5325
5299
  */
@@ -5363,6 +5337,21 @@ class ScInputQuantityComponent extends AbstractTuiNullableControl {
5363
5337
  get focused() {
5364
5338
  return !!this.numberInput?.focused;
5365
5339
  }
5340
+ /**
5341
+ * Количество знаков после запятой: по шагу, но не меньше двух.
5342
+ */
5343
+ get precision() {
5344
+ return Math.max(this.minPrecision, ScNumberHelper.getDecimalsCount(this.step));
5345
+ }
5346
+ /**
5347
+ * Настройки формата числа поля. Точность задаётся только для шага с тремя и более знаками после запятой, иначе действует общая настройка.
5348
+ */
5349
+ get numberFormat() {
5350
+ if (this.numberFormatCache?.step !== this.step) {
5351
+ this.numberFormatCache = { step: this.step, format: this.precision > this.minPrecision ? { precision: this.precision, decimalMode: 'not-zero' } : {} };
5352
+ }
5353
+ return this.numberFormatCache.format;
5354
+ }
5366
5355
  /**
5367
5356
  * Элемент формы ввода количества.
5368
5357
  */
@@ -5435,7 +5424,7 @@ class ScInputQuantityComponent extends AbstractTuiNullableControl {
5435
5424
  this.clickClearEvent.emit();
5436
5425
  }
5437
5426
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScInputQuantityComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
5438
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ScInputQuantityComponent, isStandalone: true, selector: "sc-input-quantity", inputs: { step: "step", showLoader: "showLoader", showCross: "showCross", ignoreStepValidators: "ignoreStepValidators", appearance: "appearance", isDisabled: "isDisabled", size: "size" }, outputs: { clickClearEvent: "clickClearEvent" }, host: { listeners: { "keydown.arrowDown": "onArrow(-step)", "keydown.arrowUp": "onArrow(step)" }, properties: { "attr.data-appearance": "this.appearance", "attr.data-disabled": "this.isDisabled", "attr.data-size": "this.size" } }, viewQueries: [{ propertyName: "numberInput", first: true, predicate: TuiInputNumberComponent, descendants: true }], usesInheritance: true, ngImport: i0, template: "<tui-loader\n *ngIf=\"formControl\"\n class=\"w-full\"\n [overlay]=\"true\"\n [showLoader]=\"showLoader\"\n [size]=\"size\"\n>\n <div class=\"flex items-center gap-1 text-center\">\n <div class=\"field-with-button flex grow rounded-xl\">\n <button\n tuiIconButton\n [appearance]=\"appearance\"\n [size]=\"size\"\n [disabled]=\"!numberInput['canDecrement']\"\n (click.prevent)=\"decrement(true)\"\n >\n <tui-icon icon=\"@tui.minus\"></tui-icon>\n </button>\n <tui-input-number\n #numberInput\n [formControl]=\"formControl\"\n [tuiHint]=\"([] | tuiFieldError | async)?.message\"\n [tuiTextfieldLabelOutside]=\"true\"\n [min]=\"ignoreStepValidators ? 1 : step\"\n [tuiTextfieldSize]=\"size\"\n [required]=\"true\"\n [style.text-align]=\"'center'\"\n [style.font-weight]=\"700\"\n (focusin)=\"$any($event.target).select()\"\n oncontextmenu=\"return false;\"\n (keydown.arrowDown)=\"(0)\"\n (keydown.arrowUp)=\"(0)\"\n class=\"grow\"\n >\n </tui-input-number>\n <button\n tuiIconButton\n [appearance]=\"appearance\"\n [size]=\"size\"\n [disabled]=\"!numberInput['canIncrement']\"\n (click.prevent)=\"incident(true)\"\n >\n <tui-icon icon=\"@tui.plus\"></tui-icon>\n </button>\n </div>\n <button\n *ngIf=\"showCross\"\n tuiIconButton\n appearance=\"secondary\"\n [size]=\"size\"\n [disabled]=\"!numberInput['canIncrement']\"\n (click.prevent)=\"clear()\"\n >\n <tui-icon icon=\"@tui.x\"></tui-icon>\n </button>\n </div>\n</tui-loader>\n", styles: [":host [data-size=l]{--tui-height-l: var(--tui-height-m);--tui-font-text-m: bold .875rem/1.25rem var(--tui-font-text);--tui-padding-l: 0}:host [data-size=m]{--tui-height-m: var(--tui-height-s);--tui-font-text-s: bold .75rem/1rem var(--tui-font-text);--tui-padding-m: 0}:host [data-size=m] tui-svg{font-size:12px!important}:host [data-size=s]{--tui-height-s: var(--tui-height-xs);--tui-font-text-s: bold .75rem/1rem var(--tui-font-text);--tui-padding-s: 0}:host [data-size=s] tui-svg{font-size:12px!important}:host[data-disabled=true]{pointer-events:none;opacity:var(--tui-disabled-opacity)}:host[data-appearance=secondary] .field-with-button{background-color:var(--tui-background-base);box-shadow:inset 0 0 0 .0625rem var(--tui-background-neutral-1-hover)}:host[data-appearance=primary] .field-with-button{background-color:var(--tui-status-warning-pale)}:host .field-with-button:has(._invalid){background-color:var(--tui-status-negative-pale)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i2$2.AsyncPipe, name: "async" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }, { kind: "component", type: TuiIcon, selector: "tui-icon", inputs: ["icon", "background"] }, { kind: "ngmodule", type: TuiInputNumberModule }, { kind: "component", type: i3.TuiInputNumberComponent, selector: "tui-input-number", inputs: ["min", "max", "step"] }, { kind: "directive", type: i3.TuiInputNumberDirective, selector: "tui-input-number" }, { kind: "ngmodule", type: TuiTextfieldControllerModule }, { kind: "directive", type: i3.TuiTextfieldLabelOutsideDirective, selector: "[tuiTextfieldLabelOutside]", inputs: ["tuiTextfieldLabelOutside"] }, { kind: "directive", type: i3.TuiTextfieldSizeDirective, selector: "[tuiTextfieldSize]", inputs: ["tuiTextfieldSize"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.TuiHintDirective, selector: "[tuiHint]:not(ng-container):not(ng-template)", inputs: ["tuiHintContext", "tuiHintAppearance", "tuiHint"], outputs: ["tuiHintVisible"] }, { kind: "pipe", type: TuiFieldErrorPipe, name: "tuiFieldError" }, { kind: "component", type: TuiLoader, selector: "tui-loader", inputs: ["size", "inheritColor", "overlay", "textContent", "showLoader"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5427
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ScInputQuantityComponent, isStandalone: true, selector: "sc-input-quantity", inputs: { step: "step", showLoader: "showLoader", showCross: "showCross", ignoreStepValidators: "ignoreStepValidators", appearance: "appearance", isDisabled: "isDisabled", size: "size" }, outputs: { clickClearEvent: "clickClearEvent" }, host: { listeners: { "keydown.arrowDown": "onArrow(-step)", "keydown.arrowUp": "onArrow(step)" }, properties: { "attr.data-appearance": "this.appearance", "attr.data-disabled": "this.isDisabled", "attr.data-size": "this.size" } }, viewQueries: [{ propertyName: "numberInput", first: true, predicate: TuiInputNumberComponent, descendants: true }], usesInheritance: true, ngImport: i0, template: "<tui-loader\n *ngIf=\"formControl\"\n class=\"w-full\"\n [overlay]=\"true\"\n [showLoader]=\"showLoader\"\n [size]=\"size\"\n>\n <div class=\"flex items-center gap-1 text-center\">\n <div class=\"field-with-button flex grow rounded-xl\">\n <button\n tuiIconButton\n [appearance]=\"appearance\"\n [size]=\"size\"\n [disabled]=\"!numberInput['canDecrement']\"\n (click.prevent)=\"decrement(true)\"\n >\n <tui-icon icon=\"@tui.minus\"></tui-icon>\n </button>\n <tui-input-number\n #numberInput\n [formControl]=\"formControl\"\n [tuiHint]=\"([] | tuiFieldError | async)?.message\"\n [tuiTextfieldLabelOutside]=\"true\"\n [min]=\"ignoreStepValidators ? 1 : step\"\n [tuiNumberFormat]=\"numberFormat\"\n [tuiTextfieldSize]=\"size\"\n [required]=\"true\"\n [style.text-align]=\"'center'\"\n [style.font-weight]=\"700\"\n (focusin)=\"$any($event.target).select()\"\n oncontextmenu=\"return false;\"\n (keydown.arrowDown)=\"(0)\"\n (keydown.arrowUp)=\"(0)\"\n class=\"grow\"\n >\n </tui-input-number>\n <button\n tuiIconButton\n [appearance]=\"appearance\"\n [size]=\"size\"\n [disabled]=\"!numberInput['canIncrement']\"\n (click.prevent)=\"incident(true)\"\n >\n <tui-icon icon=\"@tui.plus\"></tui-icon>\n </button>\n </div>\n <button\n *ngIf=\"showCross\"\n tuiIconButton\n appearance=\"secondary\"\n [size]=\"size\"\n [disabled]=\"!numberInput['canIncrement']\"\n (click.prevent)=\"clear()\"\n >\n <tui-icon icon=\"@tui.x\"></tui-icon>\n </button>\n </div>\n</tui-loader>\n", styles: [":host [data-size=l]{--tui-height-l: var(--tui-height-m);--tui-font-text-m: bold .875rem/1.25rem var(--tui-font-text);--tui-padding-l: 0}:host [data-size=m]{--tui-height-m: var(--tui-height-s);--tui-font-text-s: bold .75rem/1rem var(--tui-font-text);--tui-padding-m: 0}:host [data-size=m] tui-svg{font-size:12px!important}:host [data-size=s]{--tui-height-s: var(--tui-height-xs);--tui-font-text-s: bold .75rem/1rem var(--tui-font-text);--tui-padding-s: 0}:host [data-size=s] tui-svg{font-size:12px!important}:host[data-disabled=true]{pointer-events:none;opacity:var(--tui-disabled-opacity)}:host[data-appearance=secondary] .field-with-button{background-color:var(--tui-background-base);box-shadow:inset 0 0 0 .0625rem var(--tui-background-neutral-1-hover)}:host[data-appearance=primary] .field-with-button{background-color:var(--tui-status-warning-pale)}:host .field-with-button:has(._invalid){background-color:var(--tui-status-negative-pale)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i2$2.AsyncPipe, name: "async" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }, { kind: "component", type: TuiIcon, selector: "tui-icon", inputs: ["icon", "background"] }, { kind: "ngmodule", type: TuiInputNumberModule }, { kind: "component", type: i3.TuiInputNumberComponent, selector: "tui-input-number", inputs: ["min", "max", "step"] }, { kind: "directive", type: i3.TuiInputNumberDirective, selector: "tui-input-number" }, { kind: "ngmodule", type: TuiTextfieldControllerModule }, { kind: "directive", type: i3.TuiTextfieldLabelOutsideDirective, selector: "[tuiTextfieldLabelOutside]", inputs: ["tuiTextfieldLabelOutside"] }, { kind: "directive", type: i3.TuiTextfieldSizeDirective, selector: "[tuiTextfieldSize]", inputs: ["tuiTextfieldSize"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.TuiHintDirective, selector: "[tuiHint]:not(ng-container):not(ng-template)", inputs: ["tuiHintContext", "tuiHintAppearance", "tuiHint"], outputs: ["tuiHintVisible"] }, { kind: "pipe", type: TuiFieldErrorPipe, name: "tuiFieldError" }, { kind: "component", type: TuiLoader, selector: "tui-loader", inputs: ["size", "inheritColor", "overlay", "textContent", "showLoader"] }, { kind: "directive", type: TuiNumberFormat, selector: "[tuiNumberFormat]", inputs: ["tuiNumberFormat"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5439
5428
  }
5440
5429
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScInputQuantityComponent, decorators: [{
5441
5430
  type: Component,
@@ -5452,7 +5441,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5452
5441
  ...TuiHint,
5453
5442
  TuiFieldErrorPipe,
5454
5443
  TuiLoader,
5455
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<tui-loader\n *ngIf=\"formControl\"\n class=\"w-full\"\n [overlay]=\"true\"\n [showLoader]=\"showLoader\"\n [size]=\"size\"\n>\n <div class=\"flex items-center gap-1 text-center\">\n <div class=\"field-with-button flex grow rounded-xl\">\n <button\n tuiIconButton\n [appearance]=\"appearance\"\n [size]=\"size\"\n [disabled]=\"!numberInput['canDecrement']\"\n (click.prevent)=\"decrement(true)\"\n >\n <tui-icon icon=\"@tui.minus\"></tui-icon>\n </button>\n <tui-input-number\n #numberInput\n [formControl]=\"formControl\"\n [tuiHint]=\"([] | tuiFieldError | async)?.message\"\n [tuiTextfieldLabelOutside]=\"true\"\n [min]=\"ignoreStepValidators ? 1 : step\"\n [tuiTextfieldSize]=\"size\"\n [required]=\"true\"\n [style.text-align]=\"'center'\"\n [style.font-weight]=\"700\"\n (focusin)=\"$any($event.target).select()\"\n oncontextmenu=\"return false;\"\n (keydown.arrowDown)=\"(0)\"\n (keydown.arrowUp)=\"(0)\"\n class=\"grow\"\n >\n </tui-input-number>\n <button\n tuiIconButton\n [appearance]=\"appearance\"\n [size]=\"size\"\n [disabled]=\"!numberInput['canIncrement']\"\n (click.prevent)=\"incident(true)\"\n >\n <tui-icon icon=\"@tui.plus\"></tui-icon>\n </button>\n </div>\n <button\n *ngIf=\"showCross\"\n tuiIconButton\n appearance=\"secondary\"\n [size]=\"size\"\n [disabled]=\"!numberInput['canIncrement']\"\n (click.prevent)=\"clear()\"\n >\n <tui-icon icon=\"@tui.x\"></tui-icon>\n </button>\n </div>\n</tui-loader>\n", styles: [":host [data-size=l]{--tui-height-l: var(--tui-height-m);--tui-font-text-m: bold .875rem/1.25rem var(--tui-font-text);--tui-padding-l: 0}:host [data-size=m]{--tui-height-m: var(--tui-height-s);--tui-font-text-s: bold .75rem/1rem var(--tui-font-text);--tui-padding-m: 0}:host [data-size=m] tui-svg{font-size:12px!important}:host [data-size=s]{--tui-height-s: var(--tui-height-xs);--tui-font-text-s: bold .75rem/1rem var(--tui-font-text);--tui-padding-s: 0}:host [data-size=s] tui-svg{font-size:12px!important}:host[data-disabled=true]{pointer-events:none;opacity:var(--tui-disabled-opacity)}:host[data-appearance=secondary] .field-with-button{background-color:var(--tui-background-base);box-shadow:inset 0 0 0 .0625rem var(--tui-background-neutral-1-hover)}:host[data-appearance=primary] .field-with-button{background-color:var(--tui-status-warning-pale)}:host .field-with-button:has(._invalid){background-color:var(--tui-status-negative-pale)}\n"] }]
5444
+ TuiNumberFormat,
5445
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<tui-loader\n *ngIf=\"formControl\"\n class=\"w-full\"\n [overlay]=\"true\"\n [showLoader]=\"showLoader\"\n [size]=\"size\"\n>\n <div class=\"flex items-center gap-1 text-center\">\n <div class=\"field-with-button flex grow rounded-xl\">\n <button\n tuiIconButton\n [appearance]=\"appearance\"\n [size]=\"size\"\n [disabled]=\"!numberInput['canDecrement']\"\n (click.prevent)=\"decrement(true)\"\n >\n <tui-icon icon=\"@tui.minus\"></tui-icon>\n </button>\n <tui-input-number\n #numberInput\n [formControl]=\"formControl\"\n [tuiHint]=\"([] | tuiFieldError | async)?.message\"\n [tuiTextfieldLabelOutside]=\"true\"\n [min]=\"ignoreStepValidators ? 1 : step\"\n [tuiNumberFormat]=\"numberFormat\"\n [tuiTextfieldSize]=\"size\"\n [required]=\"true\"\n [style.text-align]=\"'center'\"\n [style.font-weight]=\"700\"\n (focusin)=\"$any($event.target).select()\"\n oncontextmenu=\"return false;\"\n (keydown.arrowDown)=\"(0)\"\n (keydown.arrowUp)=\"(0)\"\n class=\"grow\"\n >\n </tui-input-number>\n <button\n tuiIconButton\n [appearance]=\"appearance\"\n [size]=\"size\"\n [disabled]=\"!numberInput['canIncrement']\"\n (click.prevent)=\"incident(true)\"\n >\n <tui-icon icon=\"@tui.plus\"></tui-icon>\n </button>\n </div>\n <button\n *ngIf=\"showCross\"\n tuiIconButton\n appearance=\"secondary\"\n [size]=\"size\"\n [disabled]=\"!numberInput['canIncrement']\"\n (click.prevent)=\"clear()\"\n >\n <tui-icon icon=\"@tui.x\"></tui-icon>\n </button>\n </div>\n</tui-loader>\n", styles: [":host [data-size=l]{--tui-height-l: var(--tui-height-m);--tui-font-text-m: bold .875rem/1.25rem var(--tui-font-text);--tui-padding-l: 0}:host [data-size=m]{--tui-height-m: var(--tui-height-s);--tui-font-text-s: bold .75rem/1rem var(--tui-font-text);--tui-padding-m: 0}:host [data-size=m] tui-svg{font-size:12px!important}:host [data-size=s]{--tui-height-s: var(--tui-height-xs);--tui-font-text-s: bold .75rem/1rem var(--tui-font-text);--tui-padding-s: 0}:host [data-size=s] tui-svg{font-size:12px!important}:host[data-disabled=true]{pointer-events:none;opacity:var(--tui-disabled-opacity)}:host[data-appearance=secondary] .field-with-button{background-color:var(--tui-background-base);box-shadow:inset 0 0 0 .0625rem var(--tui-background-neutral-1-hover)}:host[data-appearance=primary] .field-with-button{background-color:var(--tui-status-warning-pale)}:host .field-with-button:has(._invalid){background-color:var(--tui-status-negative-pale)}\n"] }]
5456
5446
  }], propDecorators: { numberInput: [{
5457
5447
  type: ViewChild,
5458
5448
  args: [TuiInputNumberComponent]
@@ -8878,6 +8868,37 @@ class ScOrderItemComponent extends AbstractScPriceCard {
8878
8868
  * Признак, что необходимо отобразить элементы редактирования.
8879
8869
  */
8880
8870
  this.isShowEditControls = input(true);
8871
+ /**
8872
+ * Признак, что цена за единицу берётся из позиции, а не из текущей цены товара (например, цена, зафиксированная в заявке).
8873
+ */
8874
+ this.useLinePrice = input(false);
8875
+ /**
8876
+ * Признак, что позиция, которая не попадёт в заказ, помечается примечанием, а неактивная позиция зачёркивается.
8877
+ */
8878
+ this.markUnavailable = input(false);
8879
+ /**
8880
+ * Признак, что изменение позиции ждёт очереди или выполняется.
8881
+ */
8882
+ this.isPending = input(false);
8883
+ /**
8884
+ * Признак, что параметры позиции можно изменить в диалоге, даже если они ещё не заданы.
8885
+ */
8886
+ this.settingsAvailable = input(false);
8887
+ }
8888
+ /**
8889
+ * Оформление примечания к позиции: группа, которая не участвует в заказе, серым, остальные причины жёлтым.
8890
+ */
8891
+ get unavailableReasonAppearance() {
8892
+ return this.orderItem?.isUnavailableReasonNeutral ? 'neutral' : 'warning';
8893
+ }
8894
+ /**
8895
+ * Подсказка к количеству, когда поля количества нет: где его изменить. У неактивной позиции с примечанием количество не меняется, и подсказки нет.
8896
+ */
8897
+ get quantityHint() {
8898
+ if (this.settingsAvailable()) {
8899
+ return 'Количество и метраж меняются в окне изменения позиции';
8900
+ }
8901
+ return this.markUnavailable() && this.orderItem?.isInactive ? '' : 'Для изменения количества воспользуйтесь конфигуратором';
8881
8902
  }
8882
8903
  /**
8883
8904
  * Отображает спецификацию.
@@ -8898,13 +8919,13 @@ class ScOrderItemComponent extends AbstractScPriceCard {
8898
8919
  }
8899
8920
  }
8900
8921
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScOrderItemComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
8901
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScOrderItemComponent, isStandalone: true, selector: "sc-order-item", inputs: { isShowEditControls: { classPropertyName: "isShowEditControls", publicName: "isShowEditControls", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clickSettings: "clickSettings" }, providers: [
8922
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScOrderItemComponent, isStandalone: true, selector: "sc-order-item", inputs: { isShowEditControls: { classPropertyName: "isShowEditControls", publicName: "isShowEditControls", isSignal: true, isRequired: false, transformFunction: null }, useLinePrice: { classPropertyName: "useLinePrice", publicName: "useLinePrice", isSignal: true, isRequired: false, transformFunction: null }, markUnavailable: { classPropertyName: "markUnavailable", publicName: "markUnavailable", isSignal: true, isRequired: false, transformFunction: null }, isPending: { classPropertyName: "isPending", publicName: "isPending", isSignal: true, isRequired: false, transformFunction: null }, settingsAvailable: { classPropertyName: "settingsAvailable", publicName: "settingsAvailable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clickSettings: "clickSettings" }, providers: [
8902
8923
  SEARCH_TERM_PROVIDERS,
8903
8924
  tuiTextfieldOptionsProvider({
8904
8925
  appearance: signal('textfield'),
8905
8926
  }),
8906
8927
  tuiHintOptionsProvider({ direction: 'top', showDelay: 100, hideDelay: 100 }),
8907
- ], usesInheritance: true, ngImport: i0, template: "@if (product && orderItem) {\n <div class=\"@container relative flex w-full gap-4 rounded-xl p-1 text-body-s hover:bg-tui-background-neutral-1\">\n <sc-noindex-wrapper>\n @let isDisabled = product.isHidden || product.isNull;\n <sc-hover-image-carousel\n (click)=\"isDisabled ? null : clickCardEvent.emit()\"\n [images]=\"!isMobile ? getCardImagePreviewList(product) : [getCardImagePreview()]\"\n [isShowActions]=\"false\"\n class=\"aspect-square w-24 shrink-0 cursor-pointer self-start\"\n />\n <div class=\"flex w-full flex-col gap-y-1 gap-x-4 @5xl:flex-row @5xl:items-center @5xl:gap-5\">\n <div class=\"flex min-w-0 flex-1 flex-col gap-1\">\n <div\n [tuiHighlight]=\"(search$ | async) ?? ''\"\n class=\"text-tui-text-02\"\n >\n \u0410\u0440\u0442\u0438\u043A\u0443\u043B: {{ product.code }}\n </div>\n <div class=\"flex flex-col gap-1\">\n <a\n tuiLink\n [attr.href]=\"href ?? null\"\n [tuiHighlight]=\"(search$ | async) ?? ''\"\n (click)=\"$event.preventDefault(); clickCardEvent.emit()\"\n [class.disabled]=\"isDisabled\"\n class=\"!text-body-m-bold\"\n >\n {{ product.name }}\n </a>\n @if (product.supplierSku) {\n <span class=\"text-tui-text-02\"> \u0410\u0440\u0442\u0438\u043A\u0443\u043B \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0438\u0442\u0435\u043B\u044F: {{ product.supplierSku }} </span>\n }\n @if (product.pack) {\n <div class=\"flex items-center gap-1 text-tui-text-02\">\n \u041D\u043E\u0440\u043C\u0430 \u0443\u043F\u0430\u043A\u043E\u0432\u043A\u0438: {{ product.pack }}\n @if (product.ignoreMinCountCheck) {\n <tui-icon\n icon=\"@tui.package\"\n [tuiHint]=\"minCountHint\"\n [tuiHintShowDelay]=\"100\"\n tuiHintDirection=\"top\"\n class=\"text-body-xl text-tui-text-01 opacity-90\"\n />\n <ng-template #minCountHint>\n \u0414\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0437\u0430\u043A\u0430\u0437 <br />\n \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u043B\u044C\u043D\u043E\u0433\u043E <br />\n \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u0430\n </ng-template>\n }\n </div>\n }\n\n <sc-cost-with-discount\n scPrint=\"cost\"\n [product]=\"product\"\n />\n </div>\n @if (orderItem.specificationImgUrl) {\n <a\n tuiLink\n (click)=\"showSpecification(specificationPreview)\"\n [class.disabled]=\"isDisabled\"\n >\n \u0421\u043F\u0435\u0446\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F\n </a>\n }\n <ng-template\n #specificationPreview\n let-preview\n >\n <tui-preview\n [rotatable]=\"false\"\n [zoomable]=\"false\"\n >\n <img\n *polymorpheusOutlet=\"orderItem.specificationImgUrl as src\"\n alt=\"preview\"\n [src]=\"orderItem.specificationImgUrl\"\n />\n <button\n iconStart=\"@tui.x\"\n title=\"Close\"\n tuiIconButton\n tuiPreviewAction\n type=\"button\"\n (click)=\"preview.complete()\"\n ></button>\n </tui-preview>\n </ng-template>\n </div>\n <div class=\"grid grid-cols-1 @lg:grid-cols-3 gap-2 @2xl:gap-4 @2xl:grid-rows-1\">\n <div class=\"grid grid-cols-1 items-center @lg:gap-2 @2xl:grid-cols-2 @lg:col-span-2\">\n <!-- \u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (isShowEditControls() && showQuantityControl) {\n <!-- \u041F\u043E\u043B\u0435 \u0432\u0432\u043E\u0434\u0430 \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0442\u043E\u0432\u0430\u0440\u0430. -->\n <sc-input-quantity\n #inputQuantity\n [tuiAutoFocus]=\"autoFocuseQuantityInput()\"\n [formControl]=\"quantityControl\"\n size=\"m\"\n [showCross]=\"false\"\n [isDisabled]=\"isDisabled\"\n [step]=\"ScUnitsHelper.getProductMultiplicity(product)\"\n [ignoreStepValidators]=\"product.ignoreMinCountCheck\"\n [showLoader]=\"showLoader()\"\n (clickClearEvent)=\"clickClearEvent.emit(orderItem)\"\n (keydown.enter)=\"inputQuantity.nativeFocusableElement?.blur()\"\n class=\"w-32 shrink-0\"\n />\n } @else {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n >\n <div class=\"!text-body-m text-start\">\n \u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E:\n <strong> {{ orderItem.quantity }} \u0448\u0442.</strong>\n </div>\n </button>\n }\n\n @let showAdditionalInfo = orderItem.configurator || orderItem.height || orderItem.length || orderItem.width || orderItem.marker;\n @if (showAdditionalInfo) {\n <div class=\"flex items-center gap-2 @lg:gap-3 max-w-min\">\n <!-- \u041A\u043D\u043E\u043F\u043A\u0430 \u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u044F \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (isShowEditControls()) {\n <button\n tuiIconButton\n iconStart=\"@tui.settings\"\n [disabled]=\"isDisabled\"\n [loading]=\"showLoader()\"\n (click)=\"clickSettings.emit()\"\n size=\"s\"\n [appearance]=\"isConfiguratorHintVisible() ? 'primary' : 'secondary'\"\n class=\"shrink-0\"\n ></button>\n }\n\n <!-- \u0414\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u0430\u044F \u0438\u043D\u0444\u043E\u0440\u043C\u0430\u0446\u0438\u044F \u043E \u0442\u043E\u0432\u0430\u0440\u0435. -->\n <div\n class=\"flex flex-col gap-x-1 text-tui-base-07\"\n [class.text-body-m]=\"!isShowEditControls()\"\n >\n @if (orderItem.marker) {\n <p>\u041C\u0430\u0440\u043A\u0438\u0440\u043E\u0432\u043A\u0430: {{ orderItem.marker }}</p>\n }\n @if (orderItem.width) {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0448\u0438\u0440\u0438\u043D\u044B \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n class=\"text-body-s self-start whitespace-nowrap\"\n >\n \u0428\u0438\u0440\u0438\u043D\u0430: <span class=\"text-body-s-bold\">{{ orderItem.width }} \u043C.</span>\n </button>\n }\n @if (orderItem.height) {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0432\u044B\u0441\u043E\u0442\u044B \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n class=\"text-body-s self-start whitespace-nowrap\"\n >\n \u0412\u044B\u0441\u043E\u0442\u0430: <span class=\"text-body-s-bold\">{{ orderItem.height }} \u043C.</span>\n </button>\n } @else if (orderItem.length) {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0434\u043B\u0438\u043D\u044B \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n class=\"text-body-s self-start whitespace-nowrap\"\n >\n \u0414\u043B\u0438\u043D\u0430: <span class=\"text-body-s-bold\">{{ orderItem.length }} \u043C.</span>\n </button>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- \u0421\u0443\u043C\u043C\u0430 \u0442\u043E\u0432\u0430\u0440\u0430 \u0438 \u0441\u043A\u043B\u0430\u0434\u0441\u043A\u043E\u0435 \u043D\u0430\u043B\u0438\u0447\u0438\u0435. -->\n <div\n scPrint=\"cost\"\n class=\"flex min-w-[11rem] flex-col @lg:justify-end @2xl:!justify-center text-body-m\"\n >\n <p class=\"whitespace-nowrap\">\n \u0421\u0443\u043C\u043C\u0430:\n <strong> {{ orderItem.costRub | tuiFormatNumber: moneyFormat | async }} {{ 'RUB' | tuiCurrency }}</strong>\n </p>\n <sc-price-warehouse-stock\n scPrint=\"defaultNoPrint\"\n [product]=\"product\"\n />\n </div>\n </div>\n </div>\n </sc-noindex-wrapper>\n </div>\n} @else {\n <div class=\"tui-skeleton flex h-[8.25rem] w-full overflow-hidden rounded-xl\"></div>\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i2$2.AsyncPipe, name: "async" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }, { kind: "component", type: TuiIcon, selector: "tui-icon", inputs: ["icon", "background"] }, { kind: "ngmodule", type: TuiTextfieldControllerModule }, { kind: "directive", type: i2$3.PolymorpheusOutlet, selector: "[polymorpheusOutlet]", inputs: ["polymorpheusOutlet", "polymorpheusOutletContext"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.TuiHintDirective, selector: "[tuiHint]:not(ng-container):not(ng-template)", inputs: ["tuiHintContext", "tuiHintAppearance", "tuiHint"], outputs: ["tuiHintVisible"] }, { kind: "directive", type: TuiLink, selector: "a[tuiLink], button[tuiLink]", inputs: ["pseudo"] }, { kind: "directive", type: TuiHighlight, selector: "[tuiHighlight]", inputs: ["tuiHighlight", "tuiHighlightColor"] }, { kind: "directive", type: TuiAutoFocus, selector: "[tuiAutoFocus]", inputs: ["tuiAutoFocus"] }, { kind: "component", type: ScHoverImageCarouselComponent, selector: "sc-hover-image-carousel", inputs: ["images", "isShowActions", "productName"] }, { kind: "component", type: CostWithDiscountComponent, selector: "sc-cost-with-discount", inputs: ["product", "size"] }, { kind: "component", type: ScPriceWarehouseStockComponent, selector: "sc-price-warehouse-stock", inputs: ["classList", "product", "withStockHint", "fromMain"] }, { kind: "component", type: ScInputQuantityComponent, selector: "sc-input-quantity", inputs: ["step", "showLoader", "showCross", "ignoreStepValidators", "appearance", "isDisabled", "size"], outputs: ["clickClearEvent"] }, { kind: "pipe", type: TuiCurrencyPipe, name: "tuiCurrency" }, { kind: "pipe", type: TuiFormatNumberPipe, name: "tuiFormatNumber" }, { kind: "component", type: i2$1.TuiPreviewComponent, selector: "tui-preview", inputs: ["zoomable", "rotatable", "initialScale"] }, { kind: "directive", type: i2$1.TuiPreviewAction, selector: "[tuiPreviewAction]" }, { kind: "component", type: ScNoindexWrapperComponent, selector: "sc-noindex-wrapper" }, { kind: "component", type: TuiButtonLoading, selector: "[tuiButton][loading],[tuiIconButton][loading]", inputs: ["size", "loading"] }, { kind: "directive", type: ScPrintDirective, selector: "[scPrint]", inputs: ["scPrint"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8928
+ ], usesInheritance: true, ngImport: i0, template: "@if (product && orderItem) {\n <div class=\"@container relative flex w-full gap-4 rounded-xl p-1 text-body-s hover:bg-tui-background-neutral-1\">\n <sc-noindex-wrapper>\n @let isDisabled = orderItem.isInactive;\n @let unavailableReason = markUnavailable() ? orderItem.getUnavailableReason() : '';\n <sc-hover-image-carousel\n (click)=\"isDisabled ? null : clickCardEvent.emit()\"\n [images]=\"!isMobile ? getCardImagePreviewList(product) : [getCardImagePreview()]\"\n [isShowActions]=\"false\"\n class=\"aspect-square w-24 shrink-0 cursor-pointer self-start\"\n />\n <div class=\"flex w-full flex-col gap-y-1 gap-x-4 @5xl:flex-row @5xl:items-center @5xl:gap-5\">\n <div class=\"flex min-w-0 flex-1 flex-col gap-1\">\n @if (product.code) {\n <div\n [tuiHighlight]=\"(search$ | async) ?? ''\"\n class=\"text-tui-text-02\"\n >\n \u0410\u0440\u0442\u0438\u043A\u0443\u043B: {{ product.code }}\n </div>\n }\n <div class=\"flex flex-col gap-1\">\n <a\n tuiLink\n [attr.href]=\"href ?? null\"\n [tuiHighlight]=\"(search$ | async) ?? ''\"\n (click)=\"$event.preventDefault(); clickCardEvent.emit()\"\n [class.disabled]=\"isDisabled\"\n [class.!line-through]=\"markUnavailable() && isDisabled\"\n class=\"!text-body-m-bold\"\n >\n {{ product.name }}\n </a>\n @if (unavailableReason) {\n <div class=\"flex flex-wrap items-baseline gap-1\">\n <span class=\"text-tui-text-02\">\u041F\u0440\u0438\u043C\u0435\u0447\u0430\u043D\u0438\u0435:</span>\n <tui-badge\n [appearance]=\"unavailableReasonAppearance\"\n size=\"m\"\n class=\"!h-auto max-w-full !whitespace-normal py-0.5 text-start\"\n >\n {{ unavailableReason }}\n </tui-badge>\n </div>\n }\n @if (product.supplierSku) {\n <span class=\"text-tui-text-02\"> \u0410\u0440\u0442\u0438\u043A\u0443\u043B \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0438\u0442\u0435\u043B\u044F: {{ product.supplierSku }} </span>\n }\n @if (product.pack) {\n <div class=\"flex items-center gap-1 text-tui-text-02\">\n \u041D\u043E\u0440\u043C\u0430 \u0443\u043F\u0430\u043A\u043E\u0432\u043A\u0438: {{ product.pack }}\n @if (product.ignoreMinCountCheck) {\n <tui-icon\n icon=\"@tui.package\"\n [tuiHint]=\"minCountHint\"\n [tuiHintShowDelay]=\"100\"\n tuiHintDirection=\"top\"\n class=\"text-body-xl text-tui-text-01 opacity-90\"\n />\n <ng-template #minCountHint>\n \u0414\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0437\u0430\u043A\u0430\u0437 <br />\n \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u043B\u044C\u043D\u043E\u0433\u043E <br />\n \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u0430\n </ng-template>\n }\n </div>\n }\n\n @if (useLinePrice()) {\n @if (orderItem.costOneRub > 0 || orderItem.isOrderable) {\n <span\n scPrint=\"cost\"\n class=\"text-body-m\"\n >\n {{ orderItem.costOneRub > 0 ? orderItem.getCostInRubDescPerItem() : orderItem.getCostMessage() }}\n </span>\n }\n } @else {\n <sc-cost-with-discount\n scPrint=\"cost\"\n [product]=\"product\"\n />\n }\n </div>\n @if (orderItem.specificationImgUrl) {\n <a\n tuiLink\n (click)=\"showSpecification(specificationPreview)\"\n [class.disabled]=\"isDisabled\"\n >\n \u0421\u043F\u0435\u0446\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F\n </a>\n }\n <ng-template\n #specificationPreview\n let-preview\n >\n <tui-preview\n [rotatable]=\"false\"\n [zoomable]=\"false\"\n >\n <img\n *polymorpheusOutlet=\"orderItem.specificationImgUrl as src\"\n alt=\"preview\"\n [src]=\"orderItem.specificationImgUrl\"\n />\n <button\n iconStart=\"@tui.x\"\n title=\"Close\"\n tuiIconButton\n tuiPreviewAction\n type=\"button\"\n (click)=\"preview.complete()\"\n ></button>\n </tui-preview>\n </ng-template>\n </div>\n <div class=\"grid grid-cols-1 @lg:grid-cols-3 gap-2 @2xl:gap-4 @2xl:grid-rows-1\">\n <div class=\"grid grid-cols-1 items-center @lg:gap-2 @2xl:grid-cols-2 @lg:col-span-2\">\n <!-- \u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (isShowEditControls() && showQuantityControl) {\n <!-- \u041F\u043E\u043B\u0435 \u0432\u0432\u043E\u0434\u0430 \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0442\u043E\u0432\u0430\u0440\u0430. -->\n <sc-input-quantity\n #inputQuantity\n [tuiAutoFocus]=\"autoFocuseQuantityInput()\"\n [formControl]=\"quantityControl\"\n size=\"m\"\n [showCross]=\"false\"\n [isDisabled]=\"isDisabled\"\n [attr.inert]=\"isDisabled || null\"\n [step]=\"orderItem.quantityStep\"\n [ignoreStepValidators]=\"product.ignoreMinCountCheck\"\n [showLoader]=\"isPending()\"\n (clickClearEvent)=\"clickClearEvent.emit(orderItem)\"\n (keydown.enter)=\"inputQuantity.nativeFocusableElement?.blur()\"\n class=\"w-32 shrink-0\"\n />\n } @else {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n [tuiHint]=\"quantityHint\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n >\n <div class=\"!text-body-m text-start\">\n \u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E:\n <strong> {{ orderItem.quantity }} \u0448\u0442.</strong>\n </div>\n </button>\n }\n\n @let showAdditionalInfo = orderItem.configurator || orderItem.height || orderItem.length || orderItem.width || orderItem.marker || settingsAvailable();\n @if (showAdditionalInfo) {\n <div class=\"flex items-center gap-2 @lg:gap-3 max-w-min\">\n <!-- \u041A\u043D\u043E\u043F\u043A\u0430 \u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u044F \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (isShowEditControls()) {\n <button\n tuiIconButton\n iconStart=\"@tui.settings\"\n [disabled]=\"isDisabled\"\n [loading]=\"isPending()\"\n (click)=\"clickSettings.emit()\"\n size=\"s\"\n [appearance]=\"isConfiguratorHintVisible() ? 'primary' : 'secondary'\"\n class=\"shrink-0\"\n ></button>\n }\n\n <!-- \u0414\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u0430\u044F \u0438\u043D\u0444\u043E\u0440\u043C\u0430\u0446\u0438\u044F \u043E \u0442\u043E\u0432\u0430\u0440\u0435. -->\n <div\n class=\"flex flex-col gap-x-1 text-tui-base-07\"\n [class.text-body-m]=\"!isShowEditControls()\"\n >\n @if (orderItem.marker) {\n <p>\u041C\u0430\u0440\u043A\u0438\u0440\u043E\u0432\u043A\u0430: {{ orderItem.marker }}</p>\n }\n @if (orderItem.width) {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0448\u0438\u0440\u0438\u043D\u044B \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n class=\"text-body-s self-start whitespace-nowrap\"\n >\n \u0428\u0438\u0440\u0438\u043D\u0430: <span class=\"text-body-s-bold\">{{ orderItem.width }} \u043C.</span>\n </button>\n }\n @if (orderItem.height) {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0432\u044B\u0441\u043E\u0442\u044B \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n class=\"text-body-s self-start whitespace-nowrap\"\n >\n \u0412\u044B\u0441\u043E\u0442\u0430: <span class=\"text-body-s-bold\">{{ orderItem.height }} \u043C.</span>\n </button>\n } @else if (orderItem.length) {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0434\u043B\u0438\u043D\u044B \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n class=\"text-body-s self-start whitespace-nowrap\"\n >\n \u0414\u043B\u0438\u043D\u0430: <span class=\"text-body-s-bold\">{{ orderItem.length }} \u043C.</span>\n </button>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- \u0421\u0443\u043C\u043C\u0430 \u0442\u043E\u0432\u0430\u0440\u0430 \u0438 \u0441\u043A\u043B\u0430\u0434\u0441\u043A\u043E\u0435 \u043D\u0430\u043B\u0438\u0447\u0438\u0435. -->\n <div\n scPrint=\"cost\"\n class=\"flex min-w-[11rem] flex-col @lg:justify-end @2xl:!justify-center text-body-m\"\n >\n <p class=\"whitespace-nowrap\">\n \u0421\u0443\u043C\u043C\u0430:\n <strong> {{ orderItem.costRub | tuiFormatNumber: moneyFormat | async }} {{ 'RUB' | tuiCurrency }}</strong>\n </p>\n @if (!(markUnavailable() && product.isNull)) {\n <sc-price-warehouse-stock\n scPrint=\"defaultNoPrint\"\n [product]=\"product\"\n />\n }\n </div>\n </div>\n </div>\n </sc-noindex-wrapper>\n </div>\n} @else {\n <div class=\"tui-skeleton flex h-[8.25rem] w-full overflow-hidden rounded-xl\"></div>\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i2$2.AsyncPipe, name: "async" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }, { kind: "component", type: TuiIcon, selector: "tui-icon", inputs: ["icon", "background"] }, { kind: "ngmodule", type: TuiTextfieldControllerModule }, { kind: "directive", type: i2$3.PolymorpheusOutlet, selector: "[polymorpheusOutlet]", inputs: ["polymorpheusOutlet", "polymorpheusOutletContext"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.TuiHintDirective, selector: "[tuiHint]:not(ng-container):not(ng-template)", inputs: ["tuiHintContext", "tuiHintAppearance", "tuiHint"], outputs: ["tuiHintVisible"] }, { kind: "directive", type: TuiLink, selector: "a[tuiLink], button[tuiLink]", inputs: ["pseudo"] }, { kind: "directive", type: TuiHighlight, selector: "[tuiHighlight]", inputs: ["tuiHighlight", "tuiHighlightColor"] }, { kind: "directive", type: TuiAutoFocus, selector: "[tuiAutoFocus]", inputs: ["tuiAutoFocus"] }, { kind: "component", type: ScHoverImageCarouselComponent, selector: "sc-hover-image-carousel", inputs: ["images", "isShowActions", "productName"] }, { kind: "component", type: CostWithDiscountComponent, selector: "sc-cost-with-discount", inputs: ["product", "size"] }, { kind: "component", type: ScPriceWarehouseStockComponent, selector: "sc-price-warehouse-stock", inputs: ["classList", "product", "withStockHint", "fromMain"] }, { kind: "component", type: ScInputQuantityComponent, selector: "sc-input-quantity", inputs: ["step", "showLoader", "showCross", "ignoreStepValidators", "appearance", "isDisabled", "size"], outputs: ["clickClearEvent"] }, { kind: "pipe", type: TuiCurrencyPipe, name: "tuiCurrency" }, { kind: "pipe", type: TuiFormatNumberPipe, name: "tuiFormatNumber" }, { kind: "component", type: i2$1.TuiPreviewComponent, selector: "tui-preview", inputs: ["zoomable", "rotatable", "initialScale"] }, { kind: "directive", type: i2$1.TuiPreviewAction, selector: "[tuiPreviewAction]" }, { kind: "directive", type: TuiBadge, selector: "tui-badge,[tuiBadge]", inputs: ["size"] }, { kind: "component", type: ScNoindexWrapperComponent, selector: "sc-noindex-wrapper" }, { kind: "component", type: TuiButtonLoading, selector: "[tuiButton][loading],[tuiIconButton][loading]", inputs: ["size", "loading"] }, { kind: "directive", type: ScPrintDirective, selector: "[scPrint]", inputs: ["scPrint"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8908
8929
  }
8909
8930
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScOrderItemComponent, decorators: [{
8910
8931
  type: Component,
@@ -8931,6 +8952,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
8931
8952
  TuiCurrencyPipe,
8932
8953
  TuiFormatNumberPipe,
8933
8954
  TuiPreview,
8955
+ TuiBadge,
8934
8956
  ScNoindexWrapperComponent,
8935
8957
  TuiAppearance,
8936
8958
  TuiButtonLoading,
@@ -8941,7 +8963,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
8941
8963
  appearance: signal('textfield'),
8942
8964
  }),
8943
8965
  tuiHintOptionsProvider({ direction: 'top', showDelay: 100, hideDelay: 100 }),
8944
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (product && orderItem) {\n <div class=\"@container relative flex w-full gap-4 rounded-xl p-1 text-body-s hover:bg-tui-background-neutral-1\">\n <sc-noindex-wrapper>\n @let isDisabled = product.isHidden || product.isNull;\n <sc-hover-image-carousel\n (click)=\"isDisabled ? null : clickCardEvent.emit()\"\n [images]=\"!isMobile ? getCardImagePreviewList(product) : [getCardImagePreview()]\"\n [isShowActions]=\"false\"\n class=\"aspect-square w-24 shrink-0 cursor-pointer self-start\"\n />\n <div class=\"flex w-full flex-col gap-y-1 gap-x-4 @5xl:flex-row @5xl:items-center @5xl:gap-5\">\n <div class=\"flex min-w-0 flex-1 flex-col gap-1\">\n <div\n [tuiHighlight]=\"(search$ | async) ?? ''\"\n class=\"text-tui-text-02\"\n >\n \u0410\u0440\u0442\u0438\u043A\u0443\u043B: {{ product.code }}\n </div>\n <div class=\"flex flex-col gap-1\">\n <a\n tuiLink\n [attr.href]=\"href ?? null\"\n [tuiHighlight]=\"(search$ | async) ?? ''\"\n (click)=\"$event.preventDefault(); clickCardEvent.emit()\"\n [class.disabled]=\"isDisabled\"\n class=\"!text-body-m-bold\"\n >\n {{ product.name }}\n </a>\n @if (product.supplierSku) {\n <span class=\"text-tui-text-02\"> \u0410\u0440\u0442\u0438\u043A\u0443\u043B \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0438\u0442\u0435\u043B\u044F: {{ product.supplierSku }} </span>\n }\n @if (product.pack) {\n <div class=\"flex items-center gap-1 text-tui-text-02\">\n \u041D\u043E\u0440\u043C\u0430 \u0443\u043F\u0430\u043A\u043E\u0432\u043A\u0438: {{ product.pack }}\n @if (product.ignoreMinCountCheck) {\n <tui-icon\n icon=\"@tui.package\"\n [tuiHint]=\"minCountHint\"\n [tuiHintShowDelay]=\"100\"\n tuiHintDirection=\"top\"\n class=\"text-body-xl text-tui-text-01 opacity-90\"\n />\n <ng-template #minCountHint>\n \u0414\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0437\u0430\u043A\u0430\u0437 <br />\n \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u043B\u044C\u043D\u043E\u0433\u043E <br />\n \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u0430\n </ng-template>\n }\n </div>\n }\n\n <sc-cost-with-discount\n scPrint=\"cost\"\n [product]=\"product\"\n />\n </div>\n @if (orderItem.specificationImgUrl) {\n <a\n tuiLink\n (click)=\"showSpecification(specificationPreview)\"\n [class.disabled]=\"isDisabled\"\n >\n \u0421\u043F\u0435\u0446\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F\n </a>\n }\n <ng-template\n #specificationPreview\n let-preview\n >\n <tui-preview\n [rotatable]=\"false\"\n [zoomable]=\"false\"\n >\n <img\n *polymorpheusOutlet=\"orderItem.specificationImgUrl as src\"\n alt=\"preview\"\n [src]=\"orderItem.specificationImgUrl\"\n />\n <button\n iconStart=\"@tui.x\"\n title=\"Close\"\n tuiIconButton\n tuiPreviewAction\n type=\"button\"\n (click)=\"preview.complete()\"\n ></button>\n </tui-preview>\n </ng-template>\n </div>\n <div class=\"grid grid-cols-1 @lg:grid-cols-3 gap-2 @2xl:gap-4 @2xl:grid-rows-1\">\n <div class=\"grid grid-cols-1 items-center @lg:gap-2 @2xl:grid-cols-2 @lg:col-span-2\">\n <!-- \u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (isShowEditControls() && showQuantityControl) {\n <!-- \u041F\u043E\u043B\u0435 \u0432\u0432\u043E\u0434\u0430 \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0442\u043E\u0432\u0430\u0440\u0430. -->\n <sc-input-quantity\n #inputQuantity\n [tuiAutoFocus]=\"autoFocuseQuantityInput()\"\n [formControl]=\"quantityControl\"\n size=\"m\"\n [showCross]=\"false\"\n [isDisabled]=\"isDisabled\"\n [step]=\"ScUnitsHelper.getProductMultiplicity(product)\"\n [ignoreStepValidators]=\"product.ignoreMinCountCheck\"\n [showLoader]=\"showLoader()\"\n (clickClearEvent)=\"clickClearEvent.emit(orderItem)\"\n (keydown.enter)=\"inputQuantity.nativeFocusableElement?.blur()\"\n class=\"w-32 shrink-0\"\n />\n } @else {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n >\n <div class=\"!text-body-m text-start\">\n \u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E:\n <strong> {{ orderItem.quantity }} \u0448\u0442.</strong>\n </div>\n </button>\n }\n\n @let showAdditionalInfo = orderItem.configurator || orderItem.height || orderItem.length || orderItem.width || orderItem.marker;\n @if (showAdditionalInfo) {\n <div class=\"flex items-center gap-2 @lg:gap-3 max-w-min\">\n <!-- \u041A\u043D\u043E\u043F\u043A\u0430 \u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u044F \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (isShowEditControls()) {\n <button\n tuiIconButton\n iconStart=\"@tui.settings\"\n [disabled]=\"isDisabled\"\n [loading]=\"showLoader()\"\n (click)=\"clickSettings.emit()\"\n size=\"s\"\n [appearance]=\"isConfiguratorHintVisible() ? 'primary' : 'secondary'\"\n class=\"shrink-0\"\n ></button>\n }\n\n <!-- \u0414\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u0430\u044F \u0438\u043D\u0444\u043E\u0440\u043C\u0430\u0446\u0438\u044F \u043E \u0442\u043E\u0432\u0430\u0440\u0435. -->\n <div\n class=\"flex flex-col gap-x-1 text-tui-base-07\"\n [class.text-body-m]=\"!isShowEditControls()\"\n >\n @if (orderItem.marker) {\n <p>\u041C\u0430\u0440\u043A\u0438\u0440\u043E\u0432\u043A\u0430: {{ orderItem.marker }}</p>\n }\n @if (orderItem.width) {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0448\u0438\u0440\u0438\u043D\u044B \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n class=\"text-body-s self-start whitespace-nowrap\"\n >\n \u0428\u0438\u0440\u0438\u043D\u0430: <span class=\"text-body-s-bold\">{{ orderItem.width }} \u043C.</span>\n </button>\n }\n @if (orderItem.height) {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0432\u044B\u0441\u043E\u0442\u044B \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n class=\"text-body-s self-start whitespace-nowrap\"\n >\n \u0412\u044B\u0441\u043E\u0442\u0430: <span class=\"text-body-s-bold\">{{ orderItem.height }} \u043C.</span>\n </button>\n } @else if (orderItem.length) {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0434\u043B\u0438\u043D\u044B \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n class=\"text-body-s self-start whitespace-nowrap\"\n >\n \u0414\u043B\u0438\u043D\u0430: <span class=\"text-body-s-bold\">{{ orderItem.length }} \u043C.</span>\n </button>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- \u0421\u0443\u043C\u043C\u0430 \u0442\u043E\u0432\u0430\u0440\u0430 \u0438 \u0441\u043A\u043B\u0430\u0434\u0441\u043A\u043E\u0435 \u043D\u0430\u043B\u0438\u0447\u0438\u0435. -->\n <div\n scPrint=\"cost\"\n class=\"flex min-w-[11rem] flex-col @lg:justify-end @2xl:!justify-center text-body-m\"\n >\n <p class=\"whitespace-nowrap\">\n \u0421\u0443\u043C\u043C\u0430:\n <strong> {{ orderItem.costRub | tuiFormatNumber: moneyFormat | async }} {{ 'RUB' | tuiCurrency }}</strong>\n </p>\n <sc-price-warehouse-stock\n scPrint=\"defaultNoPrint\"\n [product]=\"product\"\n />\n </div>\n </div>\n </div>\n </sc-noindex-wrapper>\n </div>\n} @else {\n <div class=\"tui-skeleton flex h-[8.25rem] w-full overflow-hidden rounded-xl\"></div>\n}\n" }]
8966
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (product && orderItem) {\n <div class=\"@container relative flex w-full gap-4 rounded-xl p-1 text-body-s hover:bg-tui-background-neutral-1\">\n <sc-noindex-wrapper>\n @let isDisabled = orderItem.isInactive;\n @let unavailableReason = markUnavailable() ? orderItem.getUnavailableReason() : '';\n <sc-hover-image-carousel\n (click)=\"isDisabled ? null : clickCardEvent.emit()\"\n [images]=\"!isMobile ? getCardImagePreviewList(product) : [getCardImagePreview()]\"\n [isShowActions]=\"false\"\n class=\"aspect-square w-24 shrink-0 cursor-pointer self-start\"\n />\n <div class=\"flex w-full flex-col gap-y-1 gap-x-4 @5xl:flex-row @5xl:items-center @5xl:gap-5\">\n <div class=\"flex min-w-0 flex-1 flex-col gap-1\">\n @if (product.code) {\n <div\n [tuiHighlight]=\"(search$ | async) ?? ''\"\n class=\"text-tui-text-02\"\n >\n \u0410\u0440\u0442\u0438\u043A\u0443\u043B: {{ product.code }}\n </div>\n }\n <div class=\"flex flex-col gap-1\">\n <a\n tuiLink\n [attr.href]=\"href ?? null\"\n [tuiHighlight]=\"(search$ | async) ?? ''\"\n (click)=\"$event.preventDefault(); clickCardEvent.emit()\"\n [class.disabled]=\"isDisabled\"\n [class.!line-through]=\"markUnavailable() && isDisabled\"\n class=\"!text-body-m-bold\"\n >\n {{ product.name }}\n </a>\n @if (unavailableReason) {\n <div class=\"flex flex-wrap items-baseline gap-1\">\n <span class=\"text-tui-text-02\">\u041F\u0440\u0438\u043C\u0435\u0447\u0430\u043D\u0438\u0435:</span>\n <tui-badge\n [appearance]=\"unavailableReasonAppearance\"\n size=\"m\"\n class=\"!h-auto max-w-full !whitespace-normal py-0.5 text-start\"\n >\n {{ unavailableReason }}\n </tui-badge>\n </div>\n }\n @if (product.supplierSku) {\n <span class=\"text-tui-text-02\"> \u0410\u0440\u0442\u0438\u043A\u0443\u043B \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0438\u0442\u0435\u043B\u044F: {{ product.supplierSku }} </span>\n }\n @if (product.pack) {\n <div class=\"flex items-center gap-1 text-tui-text-02\">\n \u041D\u043E\u0440\u043C\u0430 \u0443\u043F\u0430\u043A\u043E\u0432\u043A\u0438: {{ product.pack }}\n @if (product.ignoreMinCountCheck) {\n <tui-icon\n icon=\"@tui.package\"\n [tuiHint]=\"minCountHint\"\n [tuiHintShowDelay]=\"100\"\n tuiHintDirection=\"top\"\n class=\"text-body-xl text-tui-text-01 opacity-90\"\n />\n <ng-template #minCountHint>\n \u0414\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0437\u0430\u043A\u0430\u0437 <br />\n \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u043B\u044C\u043D\u043E\u0433\u043E <br />\n \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u0430\n </ng-template>\n }\n </div>\n }\n\n @if (useLinePrice()) {\n @if (orderItem.costOneRub > 0 || orderItem.isOrderable) {\n <span\n scPrint=\"cost\"\n class=\"text-body-m\"\n >\n {{ orderItem.costOneRub > 0 ? orderItem.getCostInRubDescPerItem() : orderItem.getCostMessage() }}\n </span>\n }\n } @else {\n <sc-cost-with-discount\n scPrint=\"cost\"\n [product]=\"product\"\n />\n }\n </div>\n @if (orderItem.specificationImgUrl) {\n <a\n tuiLink\n (click)=\"showSpecification(specificationPreview)\"\n [class.disabled]=\"isDisabled\"\n >\n \u0421\u043F\u0435\u0446\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F\n </a>\n }\n <ng-template\n #specificationPreview\n let-preview\n >\n <tui-preview\n [rotatable]=\"false\"\n [zoomable]=\"false\"\n >\n <img\n *polymorpheusOutlet=\"orderItem.specificationImgUrl as src\"\n alt=\"preview\"\n [src]=\"orderItem.specificationImgUrl\"\n />\n <button\n iconStart=\"@tui.x\"\n title=\"Close\"\n tuiIconButton\n tuiPreviewAction\n type=\"button\"\n (click)=\"preview.complete()\"\n ></button>\n </tui-preview>\n </ng-template>\n </div>\n <div class=\"grid grid-cols-1 @lg:grid-cols-3 gap-2 @2xl:gap-4 @2xl:grid-rows-1\">\n <div class=\"grid grid-cols-1 items-center @lg:gap-2 @2xl:grid-cols-2 @lg:col-span-2\">\n <!-- \u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (isShowEditControls() && showQuantityControl) {\n <!-- \u041F\u043E\u043B\u0435 \u0432\u0432\u043E\u0434\u0430 \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0442\u043E\u0432\u0430\u0440\u0430. -->\n <sc-input-quantity\n #inputQuantity\n [tuiAutoFocus]=\"autoFocuseQuantityInput()\"\n [formControl]=\"quantityControl\"\n size=\"m\"\n [showCross]=\"false\"\n [isDisabled]=\"isDisabled\"\n [attr.inert]=\"isDisabled || null\"\n [step]=\"orderItem.quantityStep\"\n [ignoreStepValidators]=\"product.ignoreMinCountCheck\"\n [showLoader]=\"isPending()\"\n (clickClearEvent)=\"clickClearEvent.emit(orderItem)\"\n (keydown.enter)=\"inputQuantity.nativeFocusableElement?.blur()\"\n class=\"w-32 shrink-0\"\n />\n } @else {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n [tuiHint]=\"quantityHint\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n >\n <div class=\"!text-body-m text-start\">\n \u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E:\n <strong> {{ orderItem.quantity }} \u0448\u0442.</strong>\n </div>\n </button>\n }\n\n @let showAdditionalInfo = orderItem.configurator || orderItem.height || orderItem.length || orderItem.width || orderItem.marker || settingsAvailable();\n @if (showAdditionalInfo) {\n <div class=\"flex items-center gap-2 @lg:gap-3 max-w-min\">\n <!-- \u041A\u043D\u043E\u043F\u043A\u0430 \u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u044F \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (isShowEditControls()) {\n <button\n tuiIconButton\n iconStart=\"@tui.settings\"\n [disabled]=\"isDisabled\"\n [loading]=\"isPending()\"\n (click)=\"clickSettings.emit()\"\n size=\"s\"\n [appearance]=\"isConfiguratorHintVisible() ? 'primary' : 'secondary'\"\n class=\"shrink-0\"\n ></button>\n }\n\n <!-- \u0414\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u0430\u044F \u0438\u043D\u0444\u043E\u0440\u043C\u0430\u0446\u0438\u044F \u043E \u0442\u043E\u0432\u0430\u0440\u0435. -->\n <div\n class=\"flex flex-col gap-x-1 text-tui-base-07\"\n [class.text-body-m]=\"!isShowEditControls()\"\n >\n @if (orderItem.marker) {\n <p>\u041C\u0430\u0440\u043A\u0438\u0440\u043E\u0432\u043A\u0430: {{ orderItem.marker }}</p>\n }\n @if (orderItem.width) {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0448\u0438\u0440\u0438\u043D\u044B \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n class=\"text-body-s self-start whitespace-nowrap\"\n >\n \u0428\u0438\u0440\u0438\u043D\u0430: <span class=\"text-body-s-bold\">{{ orderItem.width }} \u043C.</span>\n </button>\n }\n @if (orderItem.height) {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0432\u044B\u0441\u043E\u0442\u044B \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n class=\"text-body-s self-start whitespace-nowrap\"\n >\n \u0412\u044B\u0441\u043E\u0442\u0430: <span class=\"text-body-s-bold\">{{ orderItem.height }} \u043C.</span>\n </button>\n } @else if (orderItem.length) {\n <button\n [appearance]=\"isShowEditControls() ? 'action-grayscale' : ''\"\n tuiTheme=\"light\"\n tuiHint=\"\u0414\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0434\u043B\u0438\u043D\u044B \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0442\u043E\u0440\u043E\u043C\"\n (tuiHintVisible)=\"configuratorHintVisible($event)\"\n tuiLink\n [style.text-decoration-line]=\"isShowEditControls() ? 'underline' : 'none'\"\n [class.pointer-events-none]=\"!isShowEditControls()\"\n class=\"text-body-s self-start whitespace-nowrap\"\n >\n \u0414\u043B\u0438\u043D\u0430: <span class=\"text-body-s-bold\">{{ orderItem.length }} \u043C.</span>\n </button>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- \u0421\u0443\u043C\u043C\u0430 \u0442\u043E\u0432\u0430\u0440\u0430 \u0438 \u0441\u043A\u043B\u0430\u0434\u0441\u043A\u043E\u0435 \u043D\u0430\u043B\u0438\u0447\u0438\u0435. -->\n <div\n scPrint=\"cost\"\n class=\"flex min-w-[11rem] flex-col @lg:justify-end @2xl:!justify-center text-body-m\"\n >\n <p class=\"whitespace-nowrap\">\n \u0421\u0443\u043C\u043C\u0430:\n <strong> {{ orderItem.costRub | tuiFormatNumber: moneyFormat | async }} {{ 'RUB' | tuiCurrency }}</strong>\n </p>\n @if (!(markUnavailable() && product.isNull)) {\n <sc-price-warehouse-stock\n scPrint=\"defaultNoPrint\"\n [product]=\"product\"\n />\n }\n </div>\n </div>\n </div>\n </sc-noindex-wrapper>\n </div>\n} @else {\n <div class=\"tui-skeleton flex h-[8.25rem] w-full overflow-hidden rounded-xl\"></div>\n}\n" }]
8945
8967
  }] });
8946
8968
 
8947
8969
  /* eslint-disable no-param-reassign */
@@ -8949,6 +8971,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
8949
8971
  * Компонент списка товаров корзины, заказа или черновика.
8950
8972
  */
8951
8973
  class ScOrderItemsListComponent {
8974
+ /**
8975
+ * Возвращает копию набора с добавленными или убранными ключами.
8976
+ *
8977
+ * @param keys Набор.
8978
+ * @param changed Ключи.
8979
+ * @param isIncluded Признак добавления.
8980
+ */
8981
+ static withKeys(keys, changed, isIncluded) {
8982
+ const next = new Set(keys);
8983
+ changed.forEach((key) => {
8984
+ if (isIncluded) {
8985
+ next.add(key);
8986
+ }
8987
+ else {
8988
+ next.delete(key);
8989
+ }
8990
+ });
8991
+ return next;
8992
+ }
8993
+ /**
8994
+ * Инициализирует экземпляр класса {@link ScOrderItemsListComponent}.
8995
+ * Входы выбора и пометки недоступных позиций могут прийти после списка позиций, поэтому при их изменении выбор синхронизируется заново.
8996
+ */
8952
8997
  constructor() {
8953
8998
  /**
8954
8999
  * Внутренний сигнал списка элементов корзины (товаров).
@@ -8962,6 +9007,56 @@ class ScOrderItemsListComponent {
8962
9007
  * Включить редактирование позиций.
8963
9008
  */
8964
9009
  this.editable = input(false);
9010
+ /**
9011
+ * Признак вывода позиций по группам (группа позиции или «Без группы») с заголовками и итогами. Без признака позиции выводятся одним списком без заголовков.
9012
+ */
9013
+ this.grouped = input(false);
9014
+ /**
9015
+ * Признак, что позиции, которые не попадут в заказ, помечаются примечанием и не выбираются. Неактивные позиции приглушаются и зачёркиваются.
9016
+ */
9017
+ this.markUnavailable = input(false);
9018
+ /**
9019
+ * Признак, что цена за единицу берётся из позиции, а не из текущей цены товара.
9020
+ */
9021
+ this.useLinePrice = input(false);
9022
+ /**
9023
+ * Признак, что параметры позиции меняются в диалоге товара: диалог доступен у любой активной позиции, а количество и метраж измеряемых товаров
9024
+ * меняются только в нём, даже если метраж ещё не задан.
9025
+ */
9026
+ this.measuredInDialog = input(false);
9027
+ /**
9028
+ * Откуда удаляется позиция для текста подтверждения, например «из черновика». Если не задано, позиция удаляется без подтверждения.
9029
+ * Тот же текст используется в предупреждении о нулевом количестве.
9030
+ */
9031
+ this.deleteConfirmationTarget = input(null);
9032
+ /**
9033
+ * Подпись итоговой суммы.
9034
+ */
9035
+ this.totalLabel = input('Итого');
9036
+ /**
9037
+ * Позиции, разбитые на группы. Без группировки все позиции идут одной группой без заголовка.
9038
+ */
9039
+ this.groups = computed(() => {
9040
+ const items = this.itemsSignal();
9041
+ return this.grouped() ? ScOrderItemsGroup.fromItems(items) : [new ScOrderItemsGroup('', items)];
9042
+ });
9043
+ /**
9044
+ * Идентификаторы позиций, изменение или удаление которых ждёт очереди или выполняется.
9045
+ */
9046
+ this.pendingItemsIds = signal(new Set());
9047
+ /**
9048
+ * Названия удаляемых групп.
9049
+ */
9050
+ this.deletingGroupsNames = signal(new Set());
9051
+ /**
9052
+ * Названия групп с позициями, ждущими ответа: такие группы удалить нельзя.
9053
+ */
9054
+ this.pendingGroupsNames = computed(() => {
9055
+ const pendingIds = this.pendingItemsIds();
9056
+ return new Set(this.groups()
9057
+ .filter((group) => group.items.some((item) => pendingIds.has(item.id)))
9058
+ .map((group) => group.name));
9059
+ });
8965
9060
  /**
8966
9061
  * Внутренняя группа формы для выбора позиций.
8967
9062
  */
@@ -8977,11 +9072,12 @@ class ScOrderItemsListComponent {
8977
9072
  initialValue: [],
8978
9073
  });
8979
9074
  /**
8980
- * Состояние флага «Выбрать все»: `true`, если выбраны все позиции, `false`, если ни одной, и `null`, если только часть.
9075
+ * Состояние флага «Выбрать все»: `true`, если выбраны все доступные для выбора позиции, `false`, если ни одной или выбирать нечего,
9076
+ * и `null`, если только часть.
8981
9077
  */
8982
9078
  this.allItemsSelected = computed(() => {
8983
9079
  const checkedList = this.itemsIds().map(([, checked]) => checked);
8984
- if (checkedList.every(Boolean)) {
9080
+ if (checkedList.length > 0 && checkedList.every(Boolean)) {
8985
9081
  return true;
8986
9082
  }
8987
9083
  return checkedList.some(Boolean) ? null : false;
@@ -9006,7 +9102,7 @@ class ScOrderItemsListComponent {
9006
9102
  const items = this.itemsSignal();
9007
9103
  const selectedItemsIds = this.selectedItemsIds();
9008
9104
  const selectable = this.selectable();
9009
- return ScNumberHelper.roundMoney(items.filter((item) => !selectable || selectedItemsIds.includes(item.id)).reduce((sum, item) => sum + item.costRub, 0));
9105
+ return ScOrderItem.getCostSum(items.filter((item) => !selectable || selectedItemsIds.includes(item.id)));
9010
9106
  });
9011
9107
  /**
9012
9108
  * Сервис получения и редактирования списка товаров заказа (корзина / заказ / черновик = заказ).
@@ -9017,6 +9113,10 @@ class ScOrderItemsListComponent {
9017
9113
  * Признак, что можно редактировать позиции.
9018
9114
  */
9019
9115
  this.canEdit = computed(() => this.editable() && 'updateProduct$' in this.orderItemsListLoader);
9116
+ /**
9117
+ * Признак, что группу можно удалить целиком.
9118
+ */
9119
+ this.canDeleteGroups = computed(() => this.canEdit() && 'deleteProducts$' in this.orderItemsListLoader);
9020
9120
  /**
9021
9121
  * Настройки вывода сумм в рублях.
9022
9122
  */
@@ -9030,6 +9130,21 @@ class ScOrderItemsListComponent {
9030
9130
  * Предоставляется родителем через токен {@link SC_DIALOG_SERVICE_TOKEN}.
9031
9131
  */
9032
9132
  this.scDialogService = inject(SC_DIALOG_SERVICE_TOKEN);
9133
+ /**
9134
+ * Сервис диалоговых окон Taiga для подтверждений.
9135
+ */
9136
+ this.dialogService = inject(TuiDialogService);
9137
+ /**
9138
+ * Сервис уведомлений.
9139
+ */
9140
+ this.alerts = inject(TuiAlertService);
9141
+ effect(() => {
9142
+ this.markUnavailable();
9143
+ this.selectable();
9144
+ untracked(() => {
9145
+ this.syncSelectionControls();
9146
+ });
9147
+ });
9033
9148
  }
9034
9149
  /**
9035
9150
  * Список элементов корзины (товаров). При установке синхронно обновляет контролы выбора в форме.
@@ -9040,6 +9155,7 @@ class ScOrderItemsListComponent {
9040
9155
  }
9041
9156
  /**
9042
9157
  * Синхронизирует контролы формы выбора с текущим списком товаров: удаляет контролы для отсутствующих id, добавляет для новых.
9158
+ * Контролы меняются без событий, а значение группы пересчитывается один раз в конце: на заявках в сотни позиций это заметно.
9043
9159
  */
9044
9160
  syncSelectionControls() {
9045
9161
  if (!this.selectable()) {
@@ -9049,70 +9165,125 @@ class ScOrderItemsListComponent {
9049
9165
  const currentIds = new Set(currentItems.map((item) => item.id));
9050
9166
  Object.keys(this.selectionFormGroup.controls).forEach((name) => {
9051
9167
  if (!currentIds.has(name)) {
9052
- this.selectionFormGroup.removeControl(name);
9168
+ this.selectionFormGroup.removeControl(name, { emitEvent: false });
9053
9169
  }
9054
9170
  });
9055
9171
  currentItems.forEach((item) => {
9056
- if (!this.selectionFormGroup.contains(item.id)) {
9057
- this.selectionFormGroup.addControl(item.id, new FormControl(true, { nonNullable: true }));
9172
+ const control = this.selectionFormGroup.get(item.id);
9173
+ const isSelectable = !this.markUnavailable() || item.isOrderable;
9174
+ if (!control) {
9175
+ this.selectionFormGroup.addControl(item.id, new FormControl({ value: isSelectable, disabled: !isSelectable }, { nonNullable: true }), { emitEvent: false });
9176
+ }
9177
+ else if (!isSelectable && control.enabled) {
9178
+ control.setValue(false, { emitEvent: false });
9179
+ control.disable({ emitEvent: false });
9180
+ }
9181
+ else if (isSelectable && control.disabled) {
9182
+ control.enable({ emitEvent: false });
9183
+ control.setValue(true, { emitEvent: false });
9058
9184
  }
9059
9185
  });
9186
+ this.selectionFormGroup.updateValueAndValidity();
9060
9187
  }
9061
9188
  /**
9062
- * Выбирает все позиции или снимает выбор со всех.
9189
+ * Выбирает все доступные для выбора позиции или снимает выбор со всех.
9063
9190
  *
9064
9191
  * @param event Событие изменения флага выбора всех позиций.
9065
9192
  */
9066
9193
  selectAllChangeHandler(event) {
9067
9194
  const { checked } = event.target;
9068
- this.selectionFormGroup.setValue(Object.fromEntries(Object.keys(this.selectionFormGroup.controls).map((id) => [id, checked])));
9195
+ const enabledIds = Object.entries(this.selectionFormGroup.controls)
9196
+ .filter(([, control]) => control.enabled)
9197
+ .map(([id]) => id);
9198
+ this.selectionFormGroup.patchValue(Object.fromEntries(enabledIds.map((id) => [id, checked])));
9199
+ }
9200
+ /**
9201
+ * Возвращает признак, что позиция приглушена: она неактивна или не выбрана для заказа.
9202
+ *
9203
+ * @param item Позиция.
9204
+ */
9205
+ isItemDimmed(item) {
9206
+ const isUnselected = this.selectable() && !this.selectionFormGroup.get(item.id)?.value;
9207
+ return this.markUnavailable() ? item.isInactive || (item.isOrderable && isUnselected) : isUnselected;
9208
+ }
9209
+ /**
9210
+ * Возвращает признак, что количество и метраж товара позиции меняются в диалоге товара.
9211
+ *
9212
+ * @param item Позиция.
9213
+ */
9214
+ isEditedInDialog(item) {
9215
+ return this.measuredInDialog() && ScUnitsHelper.productIsMeasurable(item.product);
9069
9216
  }
9070
9217
  /**
9071
- * Удаляет позицию.
9218
+ * Удаляет позицию, при необходимости после подтверждения. Сообщение об ошибке показывает перехватчик приложения.
9072
9219
  *
9073
9220
  * @param item Позиция товара для удаления.
9074
- * @param priceCard Компонент карточки товара.
9075
9221
  */
9076
- deleteItem(item, priceCard) {
9077
- if (!this.canEdit()) {
9222
+ deleteItem(item) {
9223
+ if (!this.canEdit() || this.pendingItemsIds().has(item.id)) {
9078
9224
  return;
9079
9225
  }
9080
- priceCard.showLoader.set(true);
9081
- this.orderItemsListLoader
9082
- .deleteProduct$(item, this.orderOptions?.orderId())
9083
- .pipe(catchError(() => {
9084
- priceCard.quantityControl.patchValue(item.quantity, {
9085
- emitEvent: false,
9086
- });
9087
- return of();
9088
- }), finalize(() => {
9089
- priceCard.showLoader.set(false);
9226
+ this.confirmDelete$(item)
9227
+ .pipe(switchMap(() => {
9228
+ this.setItemPending(item.id, true);
9229
+ return this.orderItemsListLoader.deleteProduct$(item, this.orderOptions?.orderId()).pipe(finalize(() => {
9230
+ this.setItemPending(item.id, false);
9231
+ }));
9090
9232
  }), takeUntilDestroyed(this.destroyRef))
9091
- .subscribe();
9233
+ .subscribe({ error: noop });
9234
+ }
9235
+ /**
9236
+ * Удаляет все позиции группы после подтверждения. Состав берётся на момент подтверждения.
9237
+ *
9238
+ * @param group Группа позиций.
9239
+ */
9240
+ deleteGroup(group) {
9241
+ if (!this.canDeleteGroups() || this.pendingGroupsNames().has(group.name)) {
9242
+ return;
9243
+ }
9244
+ this.confirmGroupDelete$(group)
9245
+ .pipe(switchMap(() => {
9246
+ const items = this.groups().find(({ name }) => name === group.name)?.items ?? [];
9247
+ const ids = items.map((item) => item.id);
9248
+ if (ids.length === 0) {
9249
+ return EMPTY;
9250
+ }
9251
+ this.setItemsPending(ids, true);
9252
+ this.deletingGroupsNames.update((names) => ScOrderItemsListComponent.withKeys(names, [group.name], true));
9253
+ return this.orderItemsListLoader.deleteProducts$(items, this.orderOptions?.orderId()).pipe(finalize(() => {
9254
+ this.setItemsPending(ids, false);
9255
+ this.deletingGroupsNames.update((names) => ScOrderItemsListComponent.withKeys(names, [group.name], false));
9256
+ }));
9257
+ }), takeUntilDestroyed(this.destroyRef))
9258
+ .subscribe({ error: noop });
9092
9259
  }
9093
9260
  /**
9094
- * Обновляет количество товара.
9261
+ * Обновляет количество товара. Количество неактивной позиции не меняется. Пустое или нулевое значение возвращается к сохранённому количеству.
9095
9262
  *
9096
9263
  * @param newQuantity Новое значение количества товара.
9097
9264
  * @param priceCard Компонент карточки товара.
9098
9265
  */
9099
9266
  patchItem(newQuantity, priceCard) {
9100
- if (!this.canEdit() || !priceCard.orderItem || !priceCard.product || !newQuantity) {
9267
+ const { orderItem } = priceCard;
9268
+ if (!this.canEdit() || !orderItem || !priceCard.product || orderItem.isInactive) {
9269
+ return;
9270
+ }
9271
+ if (!newQuantity) {
9272
+ priceCard.quantityControl.patchValue(orderItem.quantity, { emitEvent: false });
9273
+ this.warnEmptyQuantity();
9101
9274
  return;
9102
9275
  }
9103
- priceCard.showLoader.set(true);
9276
+ this.setItemPending(orderItem.id, true);
9104
9277
  this.orderItemsListLoader
9105
- .updateProduct$(priceCard.orderItem.id, { quantity: newQuantity }, this.orderOptions?.orderId())
9106
- .pipe(catchError(() => {
9107
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
9108
- priceCard.quantityControl.patchValue(priceCard.orderItem.quantity, {
9109
- emitEvent: false,
9110
- });
9111
- return of();
9112
- }), finalize(() => {
9113
- priceCard.showLoader.set(false);
9278
+ .updateProduct$(orderItem.id, { quantity: newQuantity }, this.orderOptions?.orderId())
9279
+ .pipe(finalize(() => {
9280
+ this.setItemPending(orderItem.id, false);
9114
9281
  }), takeUntilDestroyed(this.destroyRef))
9115
- .subscribe();
9282
+ .subscribe({
9283
+ error: () => {
9284
+ priceCard.quantityControl.patchValue((priceCard.orderItem ?? orderItem).quantity, { emitEvent: false });
9285
+ },
9286
+ });
9116
9287
  }
9117
9288
  /**
9118
9289
  * Открывает модальное окно описания товара.
@@ -9127,7 +9298,7 @@ class ScOrderItemsListComponent {
9127
9298
  }
9128
9299
  }
9129
9300
  /**
9130
- * Открывает модальное окно изменения товара или его конфигуратора.
9301
+ * Открывает модальное окно изменения товара или его конфигуратора. Если параметры позиции меняются в диалоге, окно товара открывается и у штучного товара.
9131
9302
  *
9132
9303
  * @param item Позиция товара.
9133
9304
  */
@@ -9136,7 +9307,7 @@ class ScOrderItemsListComponent {
9136
9307
  if (isConfigurator) {
9137
9308
  this.scDialogService.openConfiguratorDialog(item.product, item, this.orderOptions?.orderId());
9138
9309
  }
9139
- else if (ScUnitsHelper.productIsMeasurable(item.product)) {
9310
+ else if (this.measuredInDialog() || ScUnitsHelper.productIsMeasurable(item.product)) {
9140
9311
  this.scDialogService.openAddOrEditingCartItemDialog(item.product, item, this.orderOptions?.orderId(), true);
9141
9312
  }
9142
9313
  }
@@ -9148,8 +9319,66 @@ class ScOrderItemsListComponent {
9148
9319
  openPriceHistoryChartDialog(product) {
9149
9320
  this.scDialogService.openPriceHistoryChartDialog(product);
9150
9321
  }
9322
+ /**
9323
+ * Возвращает {@link Observable}, который выдаёт значение, когда удаление позиции подтверждено или подтверждение не требуется.
9324
+ *
9325
+ * @param item Удаляемая позиция.
9326
+ */
9327
+ confirmDelete$(item) {
9328
+ const target = this.deleteConfirmationTarget();
9329
+ if (!target) {
9330
+ return of(true);
9331
+ }
9332
+ return this.confirmRemoval$(`Удалить позицию «${item.product.name}» ${target}?`);
9333
+ }
9334
+ /**
9335
+ * Возвращает {@link Observable}, который выдаёт значение, когда удаление группы подтверждено.
9336
+ *
9337
+ * @param group Группа позиций.
9338
+ */
9339
+ confirmGroupDelete$(group) {
9340
+ const target = this.deleteConfirmationTarget();
9341
+ const source = target ? ` ${target}` : '';
9342
+ return this.confirmRemoval$(`Удалить все позиции группы «${group.name}»${source}?`);
9343
+ }
9344
+ /**
9345
+ * Возвращает {@link Observable}, который выдаёт значение, когда удаление подтверждено.
9346
+ *
9347
+ * @param label Вопрос.
9348
+ */
9349
+ confirmRemoval$(label) {
9350
+ return this.dialogService.open(TUI_CONFIRM, { label, data: { yes: 'Да, удалить', no: 'Нет, оставить' } }).pipe(filter(Boolean));
9351
+ }
9352
+ /**
9353
+ * Отмечает позицию как ожидающую ответа или снимает отметку.
9354
+ *
9355
+ * @param id Идентификатор позиции.
9356
+ * @param isPending Признак ожидания.
9357
+ */
9358
+ setItemPending(id, isPending) {
9359
+ this.setItemsPending([id], isPending);
9360
+ }
9361
+ /**
9362
+ * Ставит или снимает отметку ожидания у позиций.
9363
+ *
9364
+ * @param itemsIds Идентификаторы позиций.
9365
+ * @param isPending Признак ожидания.
9366
+ */
9367
+ setItemsPending(itemsIds, isPending) {
9368
+ this.pendingItemsIds.update((ids) => ScOrderItemsListComponent.withKeys(ids, itemsIds, isPending));
9369
+ }
9370
+ /**
9371
+ * Предупреждает, что количество не может быть пустым или нулевым, а убрать позицию можно удалением. Поле возвращается к сохранённому количеству
9372
+ * в любом списке, предупреждение показывается только в списках с подтверждением удаления.
9373
+ */
9374
+ warnEmptyQuantity() {
9375
+ const target = this.deleteConfirmationTarget();
9376
+ if (target) {
9377
+ this.alerts.open(`Количество должно быть больше нуля. Чтобы убрать позицию ${target}, удалите её.`, { appearance: 'warning' }).subscribe();
9378
+ }
9379
+ }
9151
9380
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScOrderItemsListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9152
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScOrderItemsListComponent, isStandalone: true, selector: "sc-order-items-list", inputs: { selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: false, isRequired: true, transformFunction: null } }, outputs: { selectedItemsIdsChange: "selectedItemsIdsChange" }, ngImport: i0, template: "<div\n [formGroup]=\"selectionFormGroup\"\n class=\"flex flex-col gap-5\"\n>\n <!-- \u0424\u043B\u0430\u0433 \u0432\u044B\u0431\u043E\u0440\u0430 \u0432\u0441\u0435\u0445 \u043F\u043E\u0437\u0438\u0446\u0438\u0439. -->\n @if (selectable() && itemsSignal().length) {\n <label\n tuiLabel\n scPrint=\"defaultNoPrint\"\n class=\"self-start\"\n >\n <input\n tuiCheckbox\n type=\"checkbox\"\n size=\"m\"\n [ngModel]=\"allItemsSelected()\"\n [ngModelOptions]=\"{ standalone: true }\"\n (change)=\"selectAllChangeHandler($event)\"\n />\n \u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u0441\u0435\n </label>\n }\n\n @for (item of itemsSignal(); track item.id) {\n @if ($index !== 0) {\n <hr class=\"mt-auto h-px w-full bg-tui-clear\" />\n }\n\n <div class=\"flex items-center gap-4 relative\">\n @let isActive = !selectable() || selectionFormGroup.get(item.id)?.value;\n\n <!-- \u0427\u0435\u043A\u0431\u043E\u043A\u0441 \u0432\u044B\u0431\u043E\u0440\u0430 \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (selectable()) {\n <input\n tuiCheckbox\n [disabled]=\"itemComponent.showLoader()\"\n type=\"checkbox\"\n size=\"m\"\n [formControlName]=\"item.id\"\n />\n }\n\n <!-- \u041A\u0430\u0440\u0442\u043E\u0447\u043A\u0430 \u0442\u043E\u0432\u0430\u0440\u0430. -->\n <sc-order-item\n #itemComponent\n [product]=\"item.product\"\n [orderItem]=\"item\"\n [showQuantityControl]=\"canEdit() && !item.configurator\"\n [isShowEditControls]=\"canEdit()\"\n class=\"w-full duration-200\"\n [class.opacity-60]=\"selectable() && !selectionFormGroup.get(item.id)?.value\"\n (clickCardEvent)=\"openProductInfoDialog(item.product, item, orderOptions?.orderId())\"\n (quantityValueChanges)=\"patchItem($event, itemComponent)\"\n (clickPriceHistoryEvent)=\"openPriceHistoryChartDialog(item.product)\"\n (clickSettings)=\"openSettingsDialog(item)\"\n [class.pointer-events-none]=\"!isActive\"\n />\n\n <!-- \u041A\u043D\u043E\u043F\u043A\u0430 \u0443\u0434\u0430\u043B\u0435\u043D\u0438\u044F \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (canEdit()) {\n <button\n tuiIconButton\n iconStart=\"@tui.trash\"\n (click)=\"deleteItem(item, itemComponent)\"\n [loading]=\"itemComponent.showLoader()\"\n size=\"s\"\n appearance=\"secondary\"\n class=\"!absolute left-1 top-1 lg:!relative lg:left-0 lg:top-0 lg:!self-center\"\n ></button>\n }\n </div>\n }\n\n <!-- \u0424\u0443\u0442\u0435\u0440 \u0441\u043F\u0438\u0441\u043A\u0430. -->\n <div\n scPrint=\"cost\"\n class=\"flex flex-col items-end gap-2.5\"\n >\n @let totalSum = sumSelectedItems();\n <!-- \u0418\u0442\u043E\u0433\u043E\u0432\u0430\u044F \u0441\u0443\u043C\u043C\u0430. -->\n <div class=\"text-base font-bold\">\u0418\u0442\u043E\u0433\u043E: {{ totalSum | tuiFormatNumber: moneyFormat | async }} {{ 'RUB' | tuiCurrency }}</div>\n </div>\n</div>\n", dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: TuiFormatNumberPipe, name: "tuiFormatNumber" }, { kind: "component", type: ScOrderItemComponent, selector: "sc-order-item", inputs: ["isShowEditControls"], outputs: ["clickSettings"] }, { kind: "directive", type: ScPrintDirective, selector: "[scPrint]", inputs: ["scPrint"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: TuiCheckbox, selector: "input[type=\"checkbox\"][tuiCheckbox]", inputs: ["size"] }, { kind: "directive", type: TuiLabel, selector: "label[tuiLabel]" }, { kind: "directive", type: TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }, { kind: "component", type: TuiButtonLoading, selector: "[tuiButton][loading],[tuiIconButton][loading]", inputs: ["size", "loading"] }, { kind: "pipe", type: TuiCurrencyPipe, name: "tuiCurrency" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9381
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScOrderItemsListComponent, isStandalone: true, selector: "sc-order-items-list", inputs: { selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, grouped: { classPropertyName: "grouped", publicName: "grouped", isSignal: true, isRequired: false, transformFunction: null }, markUnavailable: { classPropertyName: "markUnavailable", publicName: "markUnavailable", isSignal: true, isRequired: false, transformFunction: null }, useLinePrice: { classPropertyName: "useLinePrice", publicName: "useLinePrice", isSignal: true, isRequired: false, transformFunction: null }, measuredInDialog: { classPropertyName: "measuredInDialog", publicName: "measuredInDialog", isSignal: true, isRequired: false, transformFunction: null }, deleteConfirmationTarget: { classPropertyName: "deleteConfirmationTarget", publicName: "deleteConfirmationTarget", isSignal: true, isRequired: false, transformFunction: null }, totalLabel: { classPropertyName: "totalLabel", publicName: "totalLabel", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: false, isRequired: true, transformFunction: null } }, outputs: { selectedItemsIdsChange: "selectedItemsIdsChange" }, ngImport: i0, template: "<div\n [formGroup]=\"selectionFormGroup\"\n class=\"flex flex-col gap-5\"\n>\n <!-- \u0424\u043B\u0430\u0433 \u0432\u044B\u0431\u043E\u0440\u0430 \u0432\u0441\u0435\u0445 \u043F\u043E\u0437\u0438\u0446\u0438\u0439. -->\n @if (selectable() && itemsSignal().length) {\n <label\n tuiLabel\n scPrint=\"defaultNoPrint\"\n class=\"self-start\"\n >\n <input\n tuiCheckbox\n type=\"checkbox\"\n size=\"m\"\n [ngModel]=\"allItemsSelected()\"\n [ngModelOptions]=\"{ standalone: true }\"\n [disabled]=\"selectionFormGroup.disabled\"\n (change)=\"selectAllChangeHandler($event)\"\n />\n \u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u0441\u0435\n </label>\n }\n\n @for (group of groups(); track group.name) {\n <!-- \u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A \u0433\u0440\u0443\u043F\u043F\u044B \u043F\u043E\u0437\u0438\u0446\u0438\u0439. -->\n @if (group.name) {\n <div class=\"flex flex-wrap items-end justify-between gap-x-4 gap-y-1 border-b border-tui-base-03 pb-2\">\n <div class=\"flex min-w-0 flex-col\">\n <span class=\"break-words text-body-l-bold\">{{ group.name }}</span>\n <span class=\"text-body-s text-tui-text-02\">\u043F\u043E\u0437\u0438\u0446\u0438\u0439: {{ group.items.length }}, \u0432 \u0437\u0430\u043A\u0430\u0437: {{ group.orderableCount }}</span>\n </div>\n <div class=\"flex flex-col items-end gap-1\">\n <!-- \u041A\u043D\u043E\u043F\u043A\u0430 \u0443\u0434\u0430\u043B\u0435\u043D\u0438\u044F \u0433\u0440\u0443\u043F\u043F\u044B. -->\n @if (canDeleteGroups()) {\n <button\n tuiButton\n type=\"button\"\n appearance=\"flat\"\n size=\"xs\"\n scPrint=\"defaultNoPrint\"\n [loading]=\"deletingGroupsNames().has(group.name)\"\n [disabled]=\"pendingGroupsNames().has(group.name)\"\n (click)=\"deleteGroup(group)\"\n >\n \u0423\u0434\u0430\u043B\u0438\u0442\u044C\n </button>\n }\n <span class=\"whitespace-nowrap text-body-m-bold\">{{ group.orderableSum | tuiFormatNumber: moneyFormat | async }} {{ 'RUB' | tuiCurrency }}</span>\n </div>\n </div>\n }\n\n @for (item of group.items; track item.id) {\n @if ($index !== 0) {\n <hr class=\"mt-auto h-px w-full bg-tui-clear\" />\n }\n\n <div class=\"flex items-center gap-4 relative\">\n @let isActive = !selectable() || selectionFormGroup.get(item.id)?.value || (markUnavailable() && !item.isOrderable);\n @let isPending = pendingItemsIds().has(item.id);\n @let editedInDialog = isEditedInDialog(item);\n\n <!-- \u0427\u0435\u043A\u0431\u043E\u043A\u0441 \u0432\u044B\u0431\u043E\u0440\u0430 \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (selectable()) {\n <input\n tuiCheckbox\n type=\"checkbox\"\n size=\"m\"\n [formControlName]=\"item.id\"\n />\n }\n\n <!-- \u041A\u0430\u0440\u0442\u043E\u0447\u043A\u0430 \u0442\u043E\u0432\u0430\u0440\u0430. -->\n <sc-order-item\n #itemComponent\n [product]=\"item.product\"\n [orderItem]=\"item\"\n [showQuantityControl]=\"canEdit() && !item.configurator && !editedInDialog\"\n [isShowEditControls]=\"canEdit()\"\n [useLinePrice]=\"useLinePrice()\"\n [markUnavailable]=\"markUnavailable()\"\n [isPending]=\"isPending\"\n [settingsAvailable]=\"canEdit() && measuredInDialog() && !item.configurator && !item.isInactive\"\n class=\"w-full duration-200\"\n [class.opacity-60]=\"isItemDimmed(item)\"\n (clickCardEvent)=\"openProductInfoDialog(item.product, item, orderOptions?.orderId())\"\n (quantityValueChanges)=\"patchItem($event, itemComponent)\"\n (clickPriceHistoryEvent)=\"openPriceHistoryChartDialog(item.product)\"\n (clickSettings)=\"openSettingsDialog(item)\"\n [class.pointer-events-none]=\"!isActive\"\n />\n\n <!-- \u041A\u043D\u043E\u043F\u043A\u0430 \u0443\u0434\u0430\u043B\u0435\u043D\u0438\u044F \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (canEdit()) {\n <button\n tuiIconButton\n iconStart=\"@tui.trash\"\n (click)=\"deleteItem(item)\"\n [loading]=\"isPending\"\n size=\"s\"\n appearance=\"secondary\"\n class=\"!absolute left-1 top-1 lg:!relative lg:left-0 lg:top-0 lg:!self-center\"\n ></button>\n }\n </div>\n }\n }\n\n <!-- \u0424\u0443\u0442\u0435\u0440 \u0441\u043F\u0438\u0441\u043A\u0430. -->\n <div\n scPrint=\"cost\"\n class=\"flex flex-col items-end gap-2.5\"\n >\n @let totalSum = sumSelectedItems();\n <!-- \u0418\u0442\u043E\u0433\u043E\u0432\u0430\u044F \u0441\u0443\u043C\u043C\u0430. -->\n <div class=\"text-base font-bold\">{{ totalLabel() }}: {{ totalSum | tuiFormatNumber: moneyFormat | async }} {{ 'RUB' | tuiCurrency }}</div>\n </div>\n</div>\n", dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: TuiFormatNumberPipe, name: "tuiFormatNumber" }, { kind: "component", type: ScOrderItemComponent, selector: "sc-order-item", inputs: ["isShowEditControls", "useLinePrice", "markUnavailable", "isPending", "settingsAvailable"], outputs: ["clickSettings"] }, { kind: "directive", type: ScPrintDirective, selector: "[scPrint]", inputs: ["scPrint"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: TuiCheckbox, selector: "input[type=\"checkbox\"][tuiCheckbox]", inputs: ["size"] }, { kind: "directive", type: TuiLabel, selector: "label[tuiLabel]" }, { kind: "directive", type: TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }, { kind: "component", type: TuiButtonLoading, selector: "[tuiButton][loading],[tuiIconButton][loading]", inputs: ["size", "loading"] }, { kind: "pipe", type: TuiCurrencyPipe, name: "tuiCurrency" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9153
9382
  }
9154
9383
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScOrderItemsListComponent, decorators: [{
9155
9384
  type: Component,
@@ -9165,8 +9394,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
9165
9394
  TuiButton,
9166
9395
  TuiButtonLoading,
9167
9396
  TuiCurrencyPipe,
9168
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n [formGroup]=\"selectionFormGroup\"\n class=\"flex flex-col gap-5\"\n>\n <!-- \u0424\u043B\u0430\u0433 \u0432\u044B\u0431\u043E\u0440\u0430 \u0432\u0441\u0435\u0445 \u043F\u043E\u0437\u0438\u0446\u0438\u0439. -->\n @if (selectable() && itemsSignal().length) {\n <label\n tuiLabel\n scPrint=\"defaultNoPrint\"\n class=\"self-start\"\n >\n <input\n tuiCheckbox\n type=\"checkbox\"\n size=\"m\"\n [ngModel]=\"allItemsSelected()\"\n [ngModelOptions]=\"{ standalone: true }\"\n (change)=\"selectAllChangeHandler($event)\"\n />\n \u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u0441\u0435\n </label>\n }\n\n @for (item of itemsSignal(); track item.id) {\n @if ($index !== 0) {\n <hr class=\"mt-auto h-px w-full bg-tui-clear\" />\n }\n\n <div class=\"flex items-center gap-4 relative\">\n @let isActive = !selectable() || selectionFormGroup.get(item.id)?.value;\n\n <!-- \u0427\u0435\u043A\u0431\u043E\u043A\u0441 \u0432\u044B\u0431\u043E\u0440\u0430 \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (selectable()) {\n <input\n tuiCheckbox\n [disabled]=\"itemComponent.showLoader()\"\n type=\"checkbox\"\n size=\"m\"\n [formControlName]=\"item.id\"\n />\n }\n\n <!-- \u041A\u0430\u0440\u0442\u043E\u0447\u043A\u0430 \u0442\u043E\u0432\u0430\u0440\u0430. -->\n <sc-order-item\n #itemComponent\n [product]=\"item.product\"\n [orderItem]=\"item\"\n [showQuantityControl]=\"canEdit() && !item.configurator\"\n [isShowEditControls]=\"canEdit()\"\n class=\"w-full duration-200\"\n [class.opacity-60]=\"selectable() && !selectionFormGroup.get(item.id)?.value\"\n (clickCardEvent)=\"openProductInfoDialog(item.product, item, orderOptions?.orderId())\"\n (quantityValueChanges)=\"patchItem($event, itemComponent)\"\n (clickPriceHistoryEvent)=\"openPriceHistoryChartDialog(item.product)\"\n (clickSettings)=\"openSettingsDialog(item)\"\n [class.pointer-events-none]=\"!isActive\"\n />\n\n <!-- \u041A\u043D\u043E\u043F\u043A\u0430 \u0443\u0434\u0430\u043B\u0435\u043D\u0438\u044F \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (canEdit()) {\n <button\n tuiIconButton\n iconStart=\"@tui.trash\"\n (click)=\"deleteItem(item, itemComponent)\"\n [loading]=\"itemComponent.showLoader()\"\n size=\"s\"\n appearance=\"secondary\"\n class=\"!absolute left-1 top-1 lg:!relative lg:left-0 lg:top-0 lg:!self-center\"\n ></button>\n }\n </div>\n }\n\n <!-- \u0424\u0443\u0442\u0435\u0440 \u0441\u043F\u0438\u0441\u043A\u0430. -->\n <div\n scPrint=\"cost\"\n class=\"flex flex-col items-end gap-2.5\"\n >\n @let totalSum = sumSelectedItems();\n <!-- \u0418\u0442\u043E\u0433\u043E\u0432\u0430\u044F \u0441\u0443\u043C\u043C\u0430. -->\n <div class=\"text-base font-bold\">\u0418\u0442\u043E\u0433\u043E: {{ totalSum | tuiFormatNumber: moneyFormat | async }} {{ 'RUB' | tuiCurrency }}</div>\n </div>\n</div>\n" }]
9169
- }], propDecorators: { items: [{
9397
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n [formGroup]=\"selectionFormGroup\"\n class=\"flex flex-col gap-5\"\n>\n <!-- \u0424\u043B\u0430\u0433 \u0432\u044B\u0431\u043E\u0440\u0430 \u0432\u0441\u0435\u0445 \u043F\u043E\u0437\u0438\u0446\u0438\u0439. -->\n @if (selectable() && itemsSignal().length) {\n <label\n tuiLabel\n scPrint=\"defaultNoPrint\"\n class=\"self-start\"\n >\n <input\n tuiCheckbox\n type=\"checkbox\"\n size=\"m\"\n [ngModel]=\"allItemsSelected()\"\n [ngModelOptions]=\"{ standalone: true }\"\n [disabled]=\"selectionFormGroup.disabled\"\n (change)=\"selectAllChangeHandler($event)\"\n />\n \u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u0441\u0435\n </label>\n }\n\n @for (group of groups(); track group.name) {\n <!-- \u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A \u0433\u0440\u0443\u043F\u043F\u044B \u043F\u043E\u0437\u0438\u0446\u0438\u0439. -->\n @if (group.name) {\n <div class=\"flex flex-wrap items-end justify-between gap-x-4 gap-y-1 border-b border-tui-base-03 pb-2\">\n <div class=\"flex min-w-0 flex-col\">\n <span class=\"break-words text-body-l-bold\">{{ group.name }}</span>\n <span class=\"text-body-s text-tui-text-02\">\u043F\u043E\u0437\u0438\u0446\u0438\u0439: {{ group.items.length }}, \u0432 \u0437\u0430\u043A\u0430\u0437: {{ group.orderableCount }}</span>\n </div>\n <div class=\"flex flex-col items-end gap-1\">\n <!-- \u041A\u043D\u043E\u043F\u043A\u0430 \u0443\u0434\u0430\u043B\u0435\u043D\u0438\u044F \u0433\u0440\u0443\u043F\u043F\u044B. -->\n @if (canDeleteGroups()) {\n <button\n tuiButton\n type=\"button\"\n appearance=\"flat\"\n size=\"xs\"\n scPrint=\"defaultNoPrint\"\n [loading]=\"deletingGroupsNames().has(group.name)\"\n [disabled]=\"pendingGroupsNames().has(group.name)\"\n (click)=\"deleteGroup(group)\"\n >\n \u0423\u0434\u0430\u043B\u0438\u0442\u044C\n </button>\n }\n <span class=\"whitespace-nowrap text-body-m-bold\">{{ group.orderableSum | tuiFormatNumber: moneyFormat | async }} {{ 'RUB' | tuiCurrency }}</span>\n </div>\n </div>\n }\n\n @for (item of group.items; track item.id) {\n @if ($index !== 0) {\n <hr class=\"mt-auto h-px w-full bg-tui-clear\" />\n }\n\n <div class=\"flex items-center gap-4 relative\">\n @let isActive = !selectable() || selectionFormGroup.get(item.id)?.value || (markUnavailable() && !item.isOrderable);\n @let isPending = pendingItemsIds().has(item.id);\n @let editedInDialog = isEditedInDialog(item);\n\n <!-- \u0427\u0435\u043A\u0431\u043E\u043A\u0441 \u0432\u044B\u0431\u043E\u0440\u0430 \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (selectable()) {\n <input\n tuiCheckbox\n type=\"checkbox\"\n size=\"m\"\n [formControlName]=\"item.id\"\n />\n }\n\n <!-- \u041A\u0430\u0440\u0442\u043E\u0447\u043A\u0430 \u0442\u043E\u0432\u0430\u0440\u0430. -->\n <sc-order-item\n #itemComponent\n [product]=\"item.product\"\n [orderItem]=\"item\"\n [showQuantityControl]=\"canEdit() && !item.configurator && !editedInDialog\"\n [isShowEditControls]=\"canEdit()\"\n [useLinePrice]=\"useLinePrice()\"\n [markUnavailable]=\"markUnavailable()\"\n [isPending]=\"isPending\"\n [settingsAvailable]=\"canEdit() && measuredInDialog() && !item.configurator && !item.isInactive\"\n class=\"w-full duration-200\"\n [class.opacity-60]=\"isItemDimmed(item)\"\n (clickCardEvent)=\"openProductInfoDialog(item.product, item, orderOptions?.orderId())\"\n (quantityValueChanges)=\"patchItem($event, itemComponent)\"\n (clickPriceHistoryEvent)=\"openPriceHistoryChartDialog(item.product)\"\n (clickSettings)=\"openSettingsDialog(item)\"\n [class.pointer-events-none]=\"!isActive\"\n />\n\n <!-- \u041A\u043D\u043E\u043F\u043A\u0430 \u0443\u0434\u0430\u043B\u0435\u043D\u0438\u044F \u0442\u043E\u0432\u0430\u0440\u0430. -->\n @if (canEdit()) {\n <button\n tuiIconButton\n iconStart=\"@tui.trash\"\n (click)=\"deleteItem(item)\"\n [loading]=\"isPending\"\n size=\"s\"\n appearance=\"secondary\"\n class=\"!absolute left-1 top-1 lg:!relative lg:left-0 lg:top-0 lg:!self-center\"\n ></button>\n }\n </div>\n }\n }\n\n <!-- \u0424\u0443\u0442\u0435\u0440 \u0441\u043F\u0438\u0441\u043A\u0430. -->\n <div\n scPrint=\"cost\"\n class=\"flex flex-col items-end gap-2.5\"\n >\n @let totalSum = sumSelectedItems();\n <!-- \u0418\u0442\u043E\u0433\u043E\u0432\u0430\u044F \u0441\u0443\u043C\u043C\u0430. -->\n <div class=\"text-base font-bold\">{{ totalLabel() }}: {{ totalSum | tuiFormatNumber: moneyFormat | async }} {{ 'RUB' | tuiCurrency }}</div>\n </div>\n</div>\n" }]
9398
+ }], ctorParameters: () => [], propDecorators: { items: [{
9170
9399
  type: Input,
9171
9400
  args: [{ required: true }]
9172
9401
  }] } });
@@ -9191,6 +9420,18 @@ class ScOrderItemsListWithActionsComponent {
9191
9420
  * Включить редактирование позиций.
9192
9421
  */
9193
9422
  this.editable = input(false);
9423
+ /**
9424
+ * Признак, что цена за единицу берётся из позиции, а не из текущей цены товара.
9425
+ */
9426
+ this.useLinePrice = input(false);
9427
+ /**
9428
+ * Откуда удаляется позиция для текста подтверждения, например «из черновика». Если не задано, позиция удаляется без подтверждения.
9429
+ */
9430
+ this.deleteConfirmationTarget = input(null);
9431
+ /**
9432
+ * Подпись итоговой суммы.
9433
+ */
9434
+ this.totalLabel = input('Итого');
9194
9435
  /**
9195
9436
  * Позиции, доступные для заказа.
9196
9437
  */
@@ -9214,11 +9455,11 @@ class ScOrderItemsListWithActionsComponent {
9214
9455
  }, { allowSignalWrites: true });
9215
9456
  }
9216
9457
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScOrderItemsListWithActionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9217
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScOrderItemsListWithActionsComponent, isStandalone: true, selector: "sc-order-items-list-with-actions", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, selectedItemsIds: { classPropertyName: "selectedItemsIds", publicName: "selectedItemsIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedItemsIds: "selectedItemsIdsChange" }, ngImport: i0, template: "<div class=\"flex flex-col gap-5\">\n <div class=\"flex flex-col gap-2.5\">\n @if (availableItems().length) {\n <sc-order-items-list\n [items]=\"availableItems()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n (selectedItemsIdsChange)=\"selectedItemsIds.set($event)\"\n />\n }\n\n <!-- \u0424\u0443\u0442\u0435\u0440 \u0441\u043F\u0438\u0441\u043A\u0430 \u0442\u043E\u0432\u0430\u0440\u043E\u0432. -->\n <div class=\"flex flex-col items-center sm:flex-row sm:items-start sm:flex-wrap-reverse grow gap-x-2.5 gap-y-4\">\n <ng-content select=\"[footerActions]\" />\n </div>\n </div>\n\n @if (nullItems().length) {\n <div class=\"flex flex-col gap-2.5\">\n <div class=\"text-base font-bold\">\u0422\u043E\u0432\u0430\u0440\u044B \u0438 \u0443\u0441\u043B\u0443\u0433\u0438, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043F\u0440\u0435\u0434\u043E\u0441\u0442\u0430\u0432\u043B\u044F\u044E\u0442\u0441\u044F.</div>\n\n <sc-order-items-list\n [items]=\"nullItems()\"\n [selectable]=\"false\"\n [editable]=\"editable()\"\n />\n </div>\n }\n</div>\n", dependencies: [{ kind: "component", type: ScOrderItemsListComponent, selector: "sc-order-items-list", inputs: ["selectable", "editable", "items"], outputs: ["selectedItemsIdsChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9458
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScOrderItemsListWithActionsComponent, isStandalone: true, selector: "sc-order-items-list-with-actions", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, useLinePrice: { classPropertyName: "useLinePrice", publicName: "useLinePrice", isSignal: true, isRequired: false, transformFunction: null }, deleteConfirmationTarget: { classPropertyName: "deleteConfirmationTarget", publicName: "deleteConfirmationTarget", isSignal: true, isRequired: false, transformFunction: null }, totalLabel: { classPropertyName: "totalLabel", publicName: "totalLabel", isSignal: true, isRequired: false, transformFunction: null }, selectedItemsIds: { classPropertyName: "selectedItemsIds", publicName: "selectedItemsIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedItemsIds: "selectedItemsIdsChange" }, ngImport: i0, template: "<div class=\"flex flex-col gap-5\">\n <div class=\"flex flex-col gap-2.5\">\n @if (availableItems().length) {\n <sc-order-items-list\n [items]=\"availableItems()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n [useLinePrice]=\"useLinePrice()\"\n [deleteConfirmationTarget]=\"deleteConfirmationTarget()\"\n [totalLabel]=\"totalLabel()\"\n (selectedItemsIdsChange)=\"selectedItemsIds.set($event)\"\n />\n }\n\n <!-- \u0424\u0443\u0442\u0435\u0440 \u0441\u043F\u0438\u0441\u043A\u0430 \u0442\u043E\u0432\u0430\u0440\u043E\u0432. -->\n <div class=\"flex flex-col items-center sm:flex-row sm:items-start sm:flex-wrap-reverse grow gap-x-2.5 gap-y-4\">\n <ng-content select=\"[footerActions]\" />\n </div>\n </div>\n\n @if (nullItems().length) {\n <div class=\"flex flex-col gap-2.5\">\n <div class=\"text-base font-bold\">\u0422\u043E\u0432\u0430\u0440\u044B \u0438 \u0443\u0441\u043B\u0443\u0433\u0438, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043F\u0440\u0435\u0434\u043E\u0441\u0442\u0430\u0432\u043B\u044F\u044E\u0442\u0441\u044F.</div>\n\n <sc-order-items-list\n [items]=\"nullItems()\"\n [selectable]=\"false\"\n [editable]=\"editable()\"\n [useLinePrice]=\"useLinePrice()\"\n [deleteConfirmationTarget]=\"deleteConfirmationTarget()\"\n />\n </div>\n }\n</div>\n", dependencies: [{ kind: "component", type: ScOrderItemsListComponent, selector: "sc-order-items-list", inputs: ["selectable", "editable", "grouped", "markUnavailable", "useLinePrice", "measuredInDialog", "deleteConfirmationTarget", "totalLabel", "items"], outputs: ["selectedItemsIdsChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9218
9459
  }
9219
9460
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScOrderItemsListWithActionsComponent, decorators: [{
9220
9461
  type: Component,
9221
- args: [{ standalone: true, selector: 'sc-order-items-list-with-actions', imports: [ScOrderItemsListComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex flex-col gap-5\">\n <div class=\"flex flex-col gap-2.5\">\n @if (availableItems().length) {\n <sc-order-items-list\n [items]=\"availableItems()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n (selectedItemsIdsChange)=\"selectedItemsIds.set($event)\"\n />\n }\n\n <!-- \u0424\u0443\u0442\u0435\u0440 \u0441\u043F\u0438\u0441\u043A\u0430 \u0442\u043E\u0432\u0430\u0440\u043E\u0432. -->\n <div class=\"flex flex-col items-center sm:flex-row sm:items-start sm:flex-wrap-reverse grow gap-x-2.5 gap-y-4\">\n <ng-content select=\"[footerActions]\" />\n </div>\n </div>\n\n @if (nullItems().length) {\n <div class=\"flex flex-col gap-2.5\">\n <div class=\"text-base font-bold\">\u0422\u043E\u0432\u0430\u0440\u044B \u0438 \u0443\u0441\u043B\u0443\u0433\u0438, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043F\u0440\u0435\u0434\u043E\u0441\u0442\u0430\u0432\u043B\u044F\u044E\u0442\u0441\u044F.</div>\n\n <sc-order-items-list\n [items]=\"nullItems()\"\n [selectable]=\"false\"\n [editable]=\"editable()\"\n />\n </div>\n }\n</div>\n" }]
9462
+ args: [{ standalone: true, selector: 'sc-order-items-list-with-actions', imports: [ScOrderItemsListComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex flex-col gap-5\">\n <div class=\"flex flex-col gap-2.5\">\n @if (availableItems().length) {\n <sc-order-items-list\n [items]=\"availableItems()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n [useLinePrice]=\"useLinePrice()\"\n [deleteConfirmationTarget]=\"deleteConfirmationTarget()\"\n [totalLabel]=\"totalLabel()\"\n (selectedItemsIdsChange)=\"selectedItemsIds.set($event)\"\n />\n }\n\n <!-- \u0424\u0443\u0442\u0435\u0440 \u0441\u043F\u0438\u0441\u043A\u0430 \u0442\u043E\u0432\u0430\u0440\u043E\u0432. -->\n <div class=\"flex flex-col items-center sm:flex-row sm:items-start sm:flex-wrap-reverse grow gap-x-2.5 gap-y-4\">\n <ng-content select=\"[footerActions]\" />\n </div>\n </div>\n\n @if (nullItems().length) {\n <div class=\"flex flex-col gap-2.5\">\n <div class=\"text-base font-bold\">\u0422\u043E\u0432\u0430\u0440\u044B \u0438 \u0443\u0441\u043B\u0443\u0433\u0438, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043F\u0440\u0435\u0434\u043E\u0441\u0442\u0430\u0432\u043B\u044F\u044E\u0442\u0441\u044F.</div>\n\n <sc-order-items-list\n [items]=\"nullItems()\"\n [selectable]=\"false\"\n [editable]=\"editable()\"\n [useLinePrice]=\"useLinePrice()\"\n [deleteConfirmationTarget]=\"deleteConfirmationTarget()\"\n />\n </div>\n }\n</div>\n" }]
9222
9463
  }], ctorParameters: () => [] });
9223
9464
 
9224
9465
  /**
@@ -9239,20 +9480,28 @@ class ScNewOrderItemsComponent {
9239
9480
  */
9240
9481
  this.selectable = input(true);
9241
9482
  /**
9242
- * Время обработки и принятия заказов (из настроек окружения приложения).
9483
+ * Признак, что цена за единицу берётся из позиции, а не из текущей цены товара.
9243
9484
  */
9244
- this.orderProcessingAcceptanceTime = input('');
9485
+ this.useLinePrice = input(false);
9245
9486
  /**
9246
- * Изменение выбора позиций для оформления.
9487
+ * Подпись итоговой суммы.
9488
+ */
9489
+ this.totalLabel = input('Итого');
9490
+ /**
9491
+ * Время обработки и принятия заказов (из настроек окружения приложения).
9492
+ */
9493
+ this.orderProcessingAcceptanceTime = input('');
9494
+ /**
9495
+ * Изменение выбора позиций для оформления.
9247
9496
  */
9248
9497
  this.selectedItemsIdsChange = output();
9249
9498
  }
9250
9499
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScNewOrderItemsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9251
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.2.14", type: ScNewOrderItemsComponent, isStandalone: true, selector: "sc-new-order-items", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, orderProcessingAcceptanceTime: { classPropertyName: "orderProcessingAcceptanceTime", publicName: "orderProcessingAcceptanceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedItemsIdsChange: "selectedItemsIdsChange" }, ngImport: i0, template: "<div class=\"flex flex-col gap-5\">\n <ul class=\"ml-4 list-disc text-tui-success-fill\">\n <li>\u0417\u0430\u044F\u0432\u043A\u0438 \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u044E\u0442\u0441\u044F \u043D\u0435 \u043F\u043E\u0437\u0434\u043D\u0435\u0435 {{ orderProcessingAcceptanceTime() }} \u043F\u043B\u0430\u043D\u0438\u0440\u0443\u0435\u043C\u043E\u0433\u043E \u0434\u043D\u044F \u043F\u043E\u0441\u0442\u0430\u0432\u043A\u0438.</li>\n <li>\u0417\u0430\u043A\u0430\u0437\u044B \u0441 \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u043E\u0439 \u043D\u0430 \u043F\u043E\u043D\u0435\u0434\u0435\u043B\u044C\u043D\u0438\u043A \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u044E\u0442\u0441\u044F \u0434\u043E {{ orderProcessingAcceptanceTime() }} \u043F\u044F\u0442\u043D\u0438\u0446\u044B.</li>\n <li>\u0417\u0430\u043A\u0430\u0437\u044B, \u0441\u043E\u0437\u0434\u0430\u043D\u043D\u044B\u0435 \u043F\u043E\u0437\u0434\u043D\u0435\u0435 {{ orderProcessingAcceptanceTime() }} \u043F\u044F\u0442\u043D\u0438\u0446\u044B \u0438\u043B\u0438 \u0432 \u0441\u0443\u0431\u0431\u043E\u0442\u0443/\u0432\u043E\u0441\u043A\u0440\u0435\u0441\u0435\u043D\u044C\u0435 \u0431\u0443\u0434\u0443\u0442 \u043E\u0431\u0440\u0430\u0431\u043E\u0442\u0430\u043D\u044B \u0432 \u0431\u0443\u0434\u043D\u0438\u0435 \u0434\u043D\u0438.</li>\n <li>\n \u0423\u043F\u0440\u0430\u0432\u043B\u044F\u0442\u044C \u0441\u043F\u0438\u0441\u043A\u043E\u043C \u043A\u043E\u043D\u0442\u0440\u0430\u0433\u0435\u043D\u0442\u043E\u0432 \u0432\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u0432\n <a\n tuiLink\n routerLink=\"/profile\"\n >\n \u043B\u0438\u0447\u043D\u043E\u043C \u043A\u0430\u0431\u0438\u043D\u0435\u0442\u0435\n </a>\n .\n </li>\n </ul>\n\n <div class=\"sub-title\">\u0421\u043E\u0441\u0442\u0430\u0432 \u0437\u0430\u043A\u0430\u0437\u0430</div>\n\n <sc-order-items-list-with-actions\n [items]=\"items()\"\n [editable]=\"editable()\"\n [selectable]=\"selectable()\"\n (selectedItemsIdsChange)=\"selectedItemsIdsChange.emit($event)\"\n >\n <ng-content\n select=\"[footerActions]\"\n ngProjectAs=\"[footerActions]\"\n />\n </sc-order-items-list-with-actions>\n</div>\n", dependencies: [{ kind: "component", type: ScOrderItemsListWithActionsComponent, selector: "sc-order-items-list-with-actions", inputs: ["items", "selectable", "editable", "selectedItemsIds"], outputs: ["selectedItemsIdsChange"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: TuiLink, selector: "a[tuiLink], button[tuiLink]", inputs: ["pseudo"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9500
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.2.14", type: ScNewOrderItemsComponent, isStandalone: true, selector: "sc-new-order-items", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, useLinePrice: { classPropertyName: "useLinePrice", publicName: "useLinePrice", isSignal: true, isRequired: false, transformFunction: null }, totalLabel: { classPropertyName: "totalLabel", publicName: "totalLabel", isSignal: true, isRequired: false, transformFunction: null }, orderProcessingAcceptanceTime: { classPropertyName: "orderProcessingAcceptanceTime", publicName: "orderProcessingAcceptanceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedItemsIdsChange: "selectedItemsIdsChange" }, ngImport: i0, template: "<div class=\"flex flex-col gap-5\">\n <ul class=\"ml-4 list-disc text-tui-success-fill\">\n <li>\u0417\u0430\u044F\u0432\u043A\u0438 \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u044E\u0442\u0441\u044F \u043D\u0435 \u043F\u043E\u0437\u0434\u043D\u0435\u0435 {{ orderProcessingAcceptanceTime() }} \u043F\u043B\u0430\u043D\u0438\u0440\u0443\u0435\u043C\u043E\u0433\u043E \u0434\u043D\u044F \u043F\u043E\u0441\u0442\u0430\u0432\u043A\u0438.</li>\n <li>\u0417\u0430\u043A\u0430\u0437\u044B \u0441 \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u043E\u0439 \u043D\u0430 \u043F\u043E\u043D\u0435\u0434\u0435\u043B\u044C\u043D\u0438\u043A \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u044E\u0442\u0441\u044F \u0434\u043E {{ orderProcessingAcceptanceTime() }} \u043F\u044F\u0442\u043D\u0438\u0446\u044B.</li>\n <li>\u0417\u0430\u043A\u0430\u0437\u044B, \u0441\u043E\u0437\u0434\u0430\u043D\u043D\u044B\u0435 \u043F\u043E\u0437\u0434\u043D\u0435\u0435 {{ orderProcessingAcceptanceTime() }} \u043F\u044F\u0442\u043D\u0438\u0446\u044B \u0438\u043B\u0438 \u0432 \u0441\u0443\u0431\u0431\u043E\u0442\u0443/\u0432\u043E\u0441\u043A\u0440\u0435\u0441\u0435\u043D\u044C\u0435 \u0431\u0443\u0434\u0443\u0442 \u043E\u0431\u0440\u0430\u0431\u043E\u0442\u0430\u043D\u044B \u0432 \u0431\u0443\u0434\u043D\u0438\u0435 \u0434\u043D\u0438.</li>\n <li>\n \u0423\u043F\u0440\u0430\u0432\u043B\u044F\u0442\u044C \u0441\u043F\u0438\u0441\u043A\u043E\u043C \u043A\u043E\u043D\u0442\u0440\u0430\u0433\u0435\u043D\u0442\u043E\u0432 \u0432\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u0432\n <a\n tuiLink\n routerLink=\"/profile\"\n >\n \u043B\u0438\u0447\u043D\u043E\u043C \u043A\u0430\u0431\u0438\u043D\u0435\u0442\u0435\n </a>\n .\n </li>\n </ul>\n\n <div class=\"sub-title\">\u0421\u043E\u0441\u0442\u0430\u0432 \u0437\u0430\u043A\u0430\u0437\u0430</div>\n\n <sc-order-items-list-with-actions\n [items]=\"items()\"\n [editable]=\"editable()\"\n [selectable]=\"selectable()\"\n [useLinePrice]=\"useLinePrice()\"\n [totalLabel]=\"totalLabel()\"\n (selectedItemsIdsChange)=\"selectedItemsIdsChange.emit($event)\"\n >\n <ng-content\n select=\"[footerActions]\"\n ngProjectAs=\"[footerActions]\"\n />\n </sc-order-items-list-with-actions>\n</div>\n", dependencies: [{ kind: "component", type: ScOrderItemsListWithActionsComponent, selector: "sc-order-items-list-with-actions", inputs: ["items", "selectable", "editable", "useLinePrice", "deleteConfirmationTarget", "totalLabel", "selectedItemsIds"], outputs: ["selectedItemsIdsChange"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: TuiLink, selector: "a[tuiLink], button[tuiLink]", inputs: ["pseudo"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9252
9501
  }
9253
9502
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScNewOrderItemsComponent, decorators: [{
9254
9503
  type: Component,
9255
- args: [{ standalone: true, selector: 'sc-new-order-items', imports: [ScOrderItemsListWithActionsComponent, RouterLink, TuiLink], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex flex-col gap-5\">\n <ul class=\"ml-4 list-disc text-tui-success-fill\">\n <li>\u0417\u0430\u044F\u0432\u043A\u0438 \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u044E\u0442\u0441\u044F \u043D\u0435 \u043F\u043E\u0437\u0434\u043D\u0435\u0435 {{ orderProcessingAcceptanceTime() }} \u043F\u043B\u0430\u043D\u0438\u0440\u0443\u0435\u043C\u043E\u0433\u043E \u0434\u043D\u044F \u043F\u043E\u0441\u0442\u0430\u0432\u043A\u0438.</li>\n <li>\u0417\u0430\u043A\u0430\u0437\u044B \u0441 \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u043E\u0439 \u043D\u0430 \u043F\u043E\u043D\u0435\u0434\u0435\u043B\u044C\u043D\u0438\u043A \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u044E\u0442\u0441\u044F \u0434\u043E {{ orderProcessingAcceptanceTime() }} \u043F\u044F\u0442\u043D\u0438\u0446\u044B.</li>\n <li>\u0417\u0430\u043A\u0430\u0437\u044B, \u0441\u043E\u0437\u0434\u0430\u043D\u043D\u044B\u0435 \u043F\u043E\u0437\u0434\u043D\u0435\u0435 {{ orderProcessingAcceptanceTime() }} \u043F\u044F\u0442\u043D\u0438\u0446\u044B \u0438\u043B\u0438 \u0432 \u0441\u0443\u0431\u0431\u043E\u0442\u0443/\u0432\u043E\u0441\u043A\u0440\u0435\u0441\u0435\u043D\u044C\u0435 \u0431\u0443\u0434\u0443\u0442 \u043E\u0431\u0440\u0430\u0431\u043E\u0442\u0430\u043D\u044B \u0432 \u0431\u0443\u0434\u043D\u0438\u0435 \u0434\u043D\u0438.</li>\n <li>\n \u0423\u043F\u0440\u0430\u0432\u043B\u044F\u0442\u044C \u0441\u043F\u0438\u0441\u043A\u043E\u043C \u043A\u043E\u043D\u0442\u0440\u0430\u0433\u0435\u043D\u0442\u043E\u0432 \u0432\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u0432\n <a\n tuiLink\n routerLink=\"/profile\"\n >\n \u043B\u0438\u0447\u043D\u043E\u043C \u043A\u0430\u0431\u0438\u043D\u0435\u0442\u0435\n </a>\n .\n </li>\n </ul>\n\n <div class=\"sub-title\">\u0421\u043E\u0441\u0442\u0430\u0432 \u0437\u0430\u043A\u0430\u0437\u0430</div>\n\n <sc-order-items-list-with-actions\n [items]=\"items()\"\n [editable]=\"editable()\"\n [selectable]=\"selectable()\"\n (selectedItemsIdsChange)=\"selectedItemsIdsChange.emit($event)\"\n >\n <ng-content\n select=\"[footerActions]\"\n ngProjectAs=\"[footerActions]\"\n />\n </sc-order-items-list-with-actions>\n</div>\n" }]
9504
+ args: [{ standalone: true, selector: 'sc-new-order-items', imports: [ScOrderItemsListWithActionsComponent, RouterLink, TuiLink], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex flex-col gap-5\">\n <ul class=\"ml-4 list-disc text-tui-success-fill\">\n <li>\u0417\u0430\u044F\u0432\u043A\u0438 \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u044E\u0442\u0441\u044F \u043D\u0435 \u043F\u043E\u0437\u0434\u043D\u0435\u0435 {{ orderProcessingAcceptanceTime() }} \u043F\u043B\u0430\u043D\u0438\u0440\u0443\u0435\u043C\u043E\u0433\u043E \u0434\u043D\u044F \u043F\u043E\u0441\u0442\u0430\u0432\u043A\u0438.</li>\n <li>\u0417\u0430\u043A\u0430\u0437\u044B \u0441 \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u043E\u0439 \u043D\u0430 \u043F\u043E\u043D\u0435\u0434\u0435\u043B\u044C\u043D\u0438\u043A \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u044E\u0442\u0441\u044F \u0434\u043E {{ orderProcessingAcceptanceTime() }} \u043F\u044F\u0442\u043D\u0438\u0446\u044B.</li>\n <li>\u0417\u0430\u043A\u0430\u0437\u044B, \u0441\u043E\u0437\u0434\u0430\u043D\u043D\u044B\u0435 \u043F\u043E\u0437\u0434\u043D\u0435\u0435 {{ orderProcessingAcceptanceTime() }} \u043F\u044F\u0442\u043D\u0438\u0446\u044B \u0438\u043B\u0438 \u0432 \u0441\u0443\u0431\u0431\u043E\u0442\u0443/\u0432\u043E\u0441\u043A\u0440\u0435\u0441\u0435\u043D\u044C\u0435 \u0431\u0443\u0434\u0443\u0442 \u043E\u0431\u0440\u0430\u0431\u043E\u0442\u0430\u043D\u044B \u0432 \u0431\u0443\u0434\u043D\u0438\u0435 \u0434\u043D\u0438.</li>\n <li>\n \u0423\u043F\u0440\u0430\u0432\u043B\u044F\u0442\u044C \u0441\u043F\u0438\u0441\u043A\u043E\u043C \u043A\u043E\u043D\u0442\u0440\u0430\u0433\u0435\u043D\u0442\u043E\u0432 \u0432\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u0432\n <a\n tuiLink\n routerLink=\"/profile\"\n >\n \u043B\u0438\u0447\u043D\u043E\u043C \u043A\u0430\u0431\u0438\u043D\u0435\u0442\u0435\n </a>\n .\n </li>\n </ul>\n\n <div class=\"sub-title\">\u0421\u043E\u0441\u0442\u0430\u0432 \u0437\u0430\u043A\u0430\u0437\u0430</div>\n\n <sc-order-items-list-with-actions\n [items]=\"items()\"\n [editable]=\"editable()\"\n [selectable]=\"selectable()\"\n [useLinePrice]=\"useLinePrice()\"\n [totalLabel]=\"totalLabel()\"\n (selectedItemsIdsChange)=\"selectedItemsIdsChange.emit($event)\"\n >\n <ng-content\n select=\"[footerActions]\"\n ngProjectAs=\"[footerActions]\"\n />\n </sc-order-items-list-with-actions>\n</div>\n" }]
9256
9505
  }] });
9257
9506
 
9258
9507
  /**
@@ -9520,8 +9769,10 @@ class ScOrderFormComponent {
9520
9769
  }
9521
9770
  /**
9522
9771
  * Обрабатывает отправку формы: валидирует и эмитит данные заказа родителю.
9772
+ * Общая ошибка сервера с прошлой отправки (например, 409) не мешает отправить форму снова, ошибки полей остаются до их исправления.
9523
9773
  */
9524
9774
  submit() {
9775
+ this.form.updateValueAndValidity({ emitEvent: false });
9525
9776
  this.form.markAllAsTouched();
9526
9777
  if (!this.form.valid) {
9527
9778
  return;
@@ -9539,7 +9790,7 @@ class ScOrderFormComponent {
9539
9790
  * @param error Ответ сервера с ошибками.
9540
9791
  */
9541
9792
  applyServerErrors(error) {
9542
- const { errors, message } = error.error;
9793
+ const { errors, message } = (error.error ?? {});
9543
9794
  if (errors) {
9544
9795
  Object.keys(errors).forEach((key) => {
9545
9796
  if (key === 'deliveryDate' && isArray(errors[key])) {
@@ -9812,61 +10063,840 @@ class ScOrderItemsListByStockComponent {
9812
10063
  */
9813
10064
  this.selectable = input(false);
9814
10065
  /**
9815
- * Включить редактирование позиций.
10066
+ * Включить редактирование позиций.
10067
+ */
10068
+ this.editable = input(false);
10069
+ /**
10070
+ * Заголовок для секций «под заказ» и «нет на складе». Если не указан — заголовок не выводится.
10071
+ */
10072
+ this.sectionHeader = input();
10073
+ /**
10074
+ * Выходные данные при изменении выбора позиций.
10075
+ */
10076
+ this.selectedItemsIds = model([]);
10077
+ /**
10078
+ * Позиции в наличии на выбранном складе (не под заказ).
10079
+ */
10080
+ this.itemsInWarehouseStock = computed(() => {
10081
+ const items = this.items();
10082
+ const warehouseId = this.warehouseId();
10083
+ return items.filter((item) => item.product.isWarehouseStockExist(warehouseId) && !item.product.onOrder);
10084
+ });
10085
+ /**
10086
+ * Позиции под заказ.
10087
+ */
10088
+ this.itemsOnOrder = computed(() => {
10089
+ return this.items().filter((item) => item.product.onOrder);
10090
+ });
10091
+ /**
10092
+ * Позиции, которых нет на выбранном складе (не под заказ).
10093
+ */
10094
+ this.itemsNotInWarehouseStock = computed(() => {
10095
+ const items = this.items();
10096
+ const warehouseId = this.warehouseId();
10097
+ return items.filter((item) => !item.product.isWarehouseStockExist(warehouseId) && !item.product.onOrder);
10098
+ });
10099
+ }
10100
+ /**
10101
+ * Обработчик изменения выбора позиций.
10102
+ *
10103
+ * @param itemsIds Идентификаторы выбранных позиций.
10104
+ * @param items Позиции.
10105
+ */
10106
+ selectedItemsHandler(itemsIds, items) {
10107
+ this.selectedItemsIds.update((previous) => [...previous.filter((id) => !items.some((item) => item.id === id) && this.items().some((item) => item.id === id)), ...itemsIds]);
10108
+ }
10109
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScOrderItemsListByStockComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10110
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScOrderItemsListByStockComponent, isStandalone: true, selector: "sc-order-items-list-by-stock", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, warehouseId: { classPropertyName: "warehouseId", publicName: "warehouseId", isSignal: true, isRequired: true, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, sectionHeader: { classPropertyName: "sectionHeader", publicName: "sectionHeader", isSignal: true, isRequired: false, transformFunction: null }, selectedItemsIds: { classPropertyName: "selectedItemsIds", publicName: "selectedItemsIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedItemsIds: "selectedItemsIdsChange" }, ngImport: i0, template: "<div class=\"flex flex-col gap-5\">\n @if (itemsInWarehouseStock().length > 0) {\n <sc-order-items-list\n [items]=\"itemsInWarehouseStock()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n (selectedItemsIdsChange)=\"selectedItemsHandler($event, itemsInWarehouseStock())\"\n />\n }\n\n @if (itemsOnOrder().length > 0) {\n <div class=\"flex flex-col gap-2.5\">\n @if (sectionHeader()) {\n <div class=\"text-base font-bold text-sc-dark-grey\">{{ sectionHeader() }}</div>\n }\n <sc-order-items-list\n [items]=\"itemsOnOrder()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n (selectedItemsIdsChange)=\"selectedItemsHandler($event, itemsOnOrder())\"\n />\n </div>\n }\n\n @if (itemsNotInWarehouseStock().length > 0) {\n <div class=\"flex flex-col gap-2.5\">\n @if (sectionHeader()) {\n <div class=\"text-base font-bold text-sc-dark-grey\">{{ sectionHeader() }}</div>\n }\n <sc-order-items-list\n [items]=\"itemsNotInWarehouseStock()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n (selectedItemsIdsChange)=\"selectedItemsHandler($event, itemsNotInWarehouseStock())\"\n />\n </div>\n }\n</div>\n", dependencies: [{ kind: "component", type: ScOrderItemsListComponent, selector: "sc-order-items-list", inputs: ["selectable", "editable", "grouped", "markUnavailable", "useLinePrice", "measuredInDialog", "deleteConfirmationTarget", "totalLabel", "items"], outputs: ["selectedItemsIdsChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10111
+ }
10112
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScOrderItemsListByStockComponent, decorators: [{
10113
+ type: Component,
10114
+ args: [{ standalone: true, selector: 'sc-order-items-list-by-stock', imports: [ScOrderItemsListComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex flex-col gap-5\">\n @if (itemsInWarehouseStock().length > 0) {\n <sc-order-items-list\n [items]=\"itemsInWarehouseStock()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n (selectedItemsIdsChange)=\"selectedItemsHandler($event, itemsInWarehouseStock())\"\n />\n }\n\n @if (itemsOnOrder().length > 0) {\n <div class=\"flex flex-col gap-2.5\">\n @if (sectionHeader()) {\n <div class=\"text-base font-bold text-sc-dark-grey\">{{ sectionHeader() }}</div>\n }\n <sc-order-items-list\n [items]=\"itemsOnOrder()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n (selectedItemsIdsChange)=\"selectedItemsHandler($event, itemsOnOrder())\"\n />\n </div>\n }\n\n @if (itemsNotInWarehouseStock().length > 0) {\n <div class=\"flex flex-col gap-2.5\">\n @if (sectionHeader()) {\n <div class=\"text-base font-bold text-sc-dark-grey\">{{ sectionHeader() }}</div>\n }\n <sc-order-items-list\n [items]=\"itemsNotInWarehouseStock()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n (selectedItemsIdsChange)=\"selectedItemsHandler($event, itemsNotInWarehouseStock())\"\n />\n </div>\n }\n</div>\n" }]
10115
+ }] });
10116
+
10117
+ /**
10118
+ * Класс-помощник для работы со списком черновиков.
10119
+ */
10120
+ class ScDraftListHelper {
10121
+ /**
10122
+ * Возвращает признак, что состав страницы не изменился: то же общее количество черновиков и те же черновики в том же порядке.
10123
+ *
10124
+ * @param items Текущие элементы списка.
10125
+ * @param total Текущее общее количество черновиков.
10126
+ * @param drafts Черновики полученной страницы.
10127
+ * @param receivedTotal Общее количество черновиков в полученных данных.
10128
+ */
10129
+ static isSamePage(items, total, drafts, receivedTotal) {
10130
+ return total === receivedTotal && items.length === drafts.length && drafts.every((draft, index) => draft.id === items.at(index)?.order.id);
10131
+ }
10132
+ /**
10133
+ * Возвращает подпись состава страницы: общее количество черновиков и номера черновиков страницы по порядку.
10134
+ *
10135
+ * @param page Страница списка.
10136
+ */
10137
+ static getSignature(page) {
10138
+ return `${page.meta.total}:${page.data.map(({ id }) => id).join(',')}`;
10139
+ }
10140
+ /**
10141
+ * Возвращает элементы новой страницы списка. Черновики, которые уже есть в списке, сохраняют раскрытие и полные данные.
10142
+ *
10143
+ * @param items Текущие элементы списка.
10144
+ * @param drafts Черновики новой страницы.
10145
+ */
10146
+ static fromPage(items, drafts) {
10147
+ return drafts.map((short) => {
10148
+ const item = items.find(({ order }) => order.id === short.id);
10149
+ return item ? ScDraftListHelper.update(item, short) : { order: short, isExpanded: false };
10150
+ });
10151
+ }
10152
+ /**
10153
+ * Возвращает список с обновлёнными краткими данными полученных черновиков, состав и порядок списка не меняются.
10154
+ * Полные данные раскрытых черновиков не меняются: их обновляет кэш черновика.
10155
+ *
10156
+ * @param items Текущие элементы списка.
10157
+ * @param drafts Полученные краткие данные черновиков.
10158
+ */
10159
+ static merge(items, drafts) {
10160
+ return items.map((item) => {
10161
+ const short = drafts.find(({ id }) => id === item.order.id);
10162
+ return short ? ScDraftListHelper.update(item, short) : item;
10163
+ });
10164
+ }
10165
+ /**
10166
+ * Возвращает номера черновиков с полными данными, у которых краткие данные отличаются: название или данные актуализации.
10167
+ *
10168
+ * @param items Текущие элементы списка.
10169
+ * @param drafts Полученные краткие данные черновиков.
10170
+ */
10171
+ static getChangedFullDraftIds(items, drafts) {
10172
+ return items
10173
+ .filter(({ order }) => order instanceof ScOrderDraft)
10174
+ .filter(({ order }) => {
10175
+ const short = drafts.find(({ id }) => id === order.id);
10176
+ if (!short) {
10177
+ return false;
10178
+ }
10179
+ const isSameActualization = short.actualization ? short.actualization.isSameAs(order.actualization) : order.actualization === undefined;
10180
+ return short.name !== order.name || !isSameActualization;
10181
+ })
10182
+ .map(({ order }) => order.id);
10183
+ }
10184
+ /**
10185
+ * Обновляет элемент списка краткими данными черновика. Полные данные черновика не заменяются.
10186
+ *
10187
+ * @param item Элемент списка.
10188
+ * @param short Краткие данные черновика.
10189
+ */
10190
+ static update(item, short) {
10191
+ if (!(item.order instanceof ScOrderDraft)) {
10192
+ // eslint-disable-next-line no-param-reassign
10193
+ item.order = short;
10194
+ }
10195
+ return item;
10196
+ }
10197
+ }
10198
+
10199
+ /**
10200
+ * Сервис текущего момента для меток и проверок срока актуализации черновиков Профстрой. Момент обновляется с интервалом проверки срока
10201
+ * актуализации одним таймером на всех подписчиков, поэтому список черновиков, страница черновика и оформление заказа видят один момент.
10202
+ * Таймер работает, только пока есть подписчики.
10203
+ */
10204
+ class ScDraftExpirationClockService {
10205
+ constructor() {
10206
+ /**
10207
+ * Интервалы обновления данных.
10208
+ */
10209
+ this.updateInterval = inject(SC_UPDATE_INTERVAL);
10210
+ /**
10211
+ * {@link Observable} текущего момента. Первый подписчик запускает таймер, после отписки всех подписчиков таймер останавливается.
10212
+ * Следующие подписчики сразу получают последний момент, а не новый: он может отставать от текущего времени не больше чем
10213
+ * на интервал проверки срока, и метки срока у нового подписчика обновятся на ближайшем тике общего таймера.
10214
+ */
10215
+ this.now$ = timer(0, this.updateInterval.orderDraftExpiration).pipe(map(() => new Date()), shareReplay({ bufferSize: 1, refCount: true }));
10216
+ }
10217
+ /**
10218
+ * Возвращает сигнал текущего момента, который обновляется общим таймером, только пока признак истинен. Вызывается в контексте
10219
+ * внедрения зависимостей, например при объявлении поля компонента: подписка живёт, пока жив компонент.
10220
+ *
10221
+ * @param active Признак, пока он истинен, момент обновляется.
10222
+ */
10223
+ nowWhile(active) {
10224
+ return toSignal(toObservable(active).pipe(switchMap((isActive) => (isActive ? this.now$ : EMPTY))), { initialValue: new Date() });
10225
+ }
10226
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScDraftExpirationClockService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
10227
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScDraftExpirationClockService, providedIn: 'root' }); }
10228
+ }
10229
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScDraftExpirationClockService, decorators: [{
10230
+ type: Injectable,
10231
+ args: [{
10232
+ providedIn: 'root',
10233
+ }]
10234
+ }] });
10235
+
10236
+ /**
10237
+ * Сервис загрузки и фонового обновления страницы списка черновиков. Предоставляется компонентом списка.
10238
+ *
10239
+ * Страница загружается по параметрам из {@link connect} с индикатором загрузки, после показа загруженной страницы сервис сообщает о её загрузке.
10240
+ *
10241
+ * Если состав страницы не изменился, черновики обновляются на месте с сохранением раскрытых строк. Если черновики появились, пропали
10242
+ * или поменялся порядок, полученная страница откладывается и показывается уведомление с предложением перезагрузить список.
10243
+ * После отказа уведомление о том же составе не повторяется. Черновики Профстрой в обработке дополнительно опрашиваются лёгким запросом,
10244
+ * текущий момент для меток срока берётся из общего источника {@link ScDraftExpirationClockService}.
10245
+ */
10246
+ class ScDraftListUpdatesService {
10247
+ /**
10248
+ * Инициализирует экземпляр класса {@link ScDraftListUpdatesService}.
10249
+ */
10250
+ constructor() {
10251
+ /**
10252
+ * Сервис для работы с черновиками заказов.
10253
+ */
10254
+ this.draftsService = inject(ScOrderDraftsService);
10255
+ /**
10256
+ * Сервис уведомлений.
10257
+ */
10258
+ this.alerts = inject(TuiAlertService);
10259
+ /**
10260
+ * Сервис текущего момента для меток срока актуализации.
10261
+ */
10262
+ this.expirationClock = inject(ScDraftExpirationClockService);
10263
+ /**
10264
+ * Ссылка для автоматического управления уничтожением зависимостей.
10265
+ */
10266
+ this.destroyRef = inject(DestroyRef);
10267
+ /**
10268
+ * Элементы текущей страницы списка.
10269
+ */
10270
+ this.pageItems = signal(null);
10271
+ /**
10272
+ * Данные пагинации текущей страницы.
10273
+ */
10274
+ this.pageMeta = signal(null);
10275
+ /**
10276
+ * Признак загрузки страницы списка по действию пользователя.
10277
+ */
10278
+ this.pageLoading = signal(false);
10279
+ /**
10280
+ * {@link Subject} события загрузки страницы списка по действию пользователя.
10281
+ */
10282
+ this.pageLoadedSubject = new Subject();
10283
+ /**
10284
+ * Страница с изменённым составом, которая ждёт решения пользователя.
10285
+ */
10286
+ this.pendingPage = null;
10287
+ /**
10288
+ * Подпись состава страницы, от перезагрузки которой пользователь отказался.
10289
+ */
10290
+ this.declinedSignature = null;
10291
+ /**
10292
+ * Номера черновиков в обработке, которых не оказалось в ответе опроса: черновики удалены, и опрашивать их не нужно.
10293
+ */
10294
+ this.missingIds = signal(new Set());
10295
+ /**
10296
+ * Номера раскрытых черновиков, перезапрос которых уже выполняется.
10297
+ */
10298
+ this.refreshingIds = new Set();
10299
+ /**
10300
+ * Подписка на показанное уведомление об изменении списка.
10301
+ */
10302
+ this.alertSubscription = null;
10303
+ /**
10304
+ * Номера черновиков Профстрой в обработке на текущей странице.
10305
+ */
10306
+ this.processingIds = computed(() => (this.pageItems() ?? [])
10307
+ .filter(({ order }) => order.isProfstroy && order.actualization?.isProcessing === true && !this.missingIds().has(order.id))
10308
+ .map(({ order }) => order.id), { equal: (previous, current) => previous.join(',') === current.join(',') });
10309
+ /**
10310
+ * Элементы текущей страницы списка.
10311
+ */
10312
+ this.items = this.pageItems.asReadonly();
10313
+ /**
10314
+ * Данные пагинации текущей страницы.
10315
+ */
10316
+ this.meta = this.pageMeta.asReadonly();
10317
+ /**
10318
+ * Текущий момент для определения состояния черновиков Профстрой из общего источника.
10319
+ */
10320
+ this.now = toSignal(this.expirationClock.now$, { initialValue: new Date() });
10321
+ /**
10322
+ * Признак загрузки страницы списка по действию пользователя (фильтры, сортировка, пагинация). Фоновые проверки его не включают.
10323
+ */
10324
+ this.loading = this.pageLoading.asReadonly();
10325
+ /**
10326
+ * {@link Observable} события загрузки страницы списка по действию пользователя: страница уже показана.
10327
+ */
10328
+ this.pageLoaded$ = this.pageLoadedSubject.asObservable();
10329
+ toObservable(this.processingIds)
10330
+ .pipe(switchMap((ids) => (ids.length > 0 ? this.draftsService.watchShortList$(ids).pipe(map((drafts) => ({ ids, drafts }))) : EMPTY)), takeUntilDestroyed(this.destroyRef))
10331
+ .subscribe(({ ids, drafts }) => {
10332
+ this.markMissing(ids, drafts);
10333
+ this.applyDrafts(drafts);
10334
+ });
10335
+ this.destroyRef.onDestroy(() => {
10336
+ this.dismissAlert();
10337
+ });
10338
+ }
10339
+ /**
10340
+ * Подключает к сервису параметры запроса текущей страницы: при каждом изменении параметров загружает страницу с индикатором загрузки
10341
+ * и сообщает о её загрузке, а по текущим параметрам в фоне проверяет изменения списка. Если страница не загрузилась, остаётся текущая,
10342
+ * а смена параметров загружает список заново.
10343
+ *
10344
+ * @param params$ {@link Observable} параметров запроса текущей страницы.
10345
+ */
10346
+ connect(params$) {
10347
+ params$
10348
+ .pipe(switchMap((params) => this.draftsService.getList$(params).pipe(tap({
10349
+ subscribe: () => {
10350
+ this.pageLoading.set(true);
10351
+ },
10352
+ finalize: () => {
10353
+ this.pageLoading.set(false);
10354
+ },
10355
+ }), catchError(() => EMPTY))), takeUntilDestroyed(this.destroyRef))
10356
+ .subscribe((page) => {
10357
+ this.setPage(page);
10358
+ this.pageLoadedSubject.next();
10359
+ });
10360
+ params$
10361
+ .pipe(distinctUntilChanged((previous, current) => previous.toString() === current.toString()), switchMap((params) => this.draftsService.watchList$(params)), takeUntilDestroyed(this.destroyRef))
10362
+ .subscribe((page) => {
10363
+ this.checkPage(page);
10364
+ });
10365
+ }
10366
+ /**
10367
+ * Подставляет в строку текущей страницы полные данные черновика. Опрос черновиков в обработке сразу учитывает их состояние.
10368
+ *
10369
+ * @param draft Полные данные черновика.
10370
+ */
10371
+ setFullDraft(draft) {
10372
+ const items = this.pageItems();
10373
+ const item = items?.find(({ order }) => order.id === draft.id);
10374
+ if (!items || !item) {
10375
+ return;
10376
+ }
10377
+ item.order = draft;
10378
+ this.pageItems.set([...items]);
10379
+ }
10380
+ /**
10381
+ * Показывает страницу, загруженную по действию пользователя: строки свёрнуты, отложенные данные и уведомление сбрасываются.
10382
+ *
10383
+ * @param page Страница списка.
10384
+ */
10385
+ setPage(page) {
10386
+ this.pendingPage = null;
10387
+ this.declinedSignature = null;
10388
+ this.missingIds.set(new Set());
10389
+ this.dismissAlert();
10390
+ this.pageItems.set(page.data.map((order) => ({ order, isExpanded: false })));
10391
+ this.pageMeta.set(page.meta);
10392
+ }
10393
+ /**
10394
+ * Сравнивает полученную в фоне страницу с текущей: при том же составе обновляет черновики на месте, иначе откладывает страницу и предлагает перезагрузить список.
10395
+ *
10396
+ * @param page Страница, полученная в фоне.
10397
+ */
10398
+ checkPage(page) {
10399
+ const items = this.pageItems();
10400
+ const meta = this.pageMeta();
10401
+ if (!items || !meta) {
10402
+ return;
10403
+ }
10404
+ if (ScDraftListHelper.isSamePage(items, meta.total, page.data, page.meta.total)) {
10405
+ this.pendingPage = null;
10406
+ this.declinedSignature = null;
10407
+ this.dismissAlert();
10408
+ this.applyDrafts(page.data);
10409
+ return;
10410
+ }
10411
+ this.pendingPage = page;
10412
+ if (ScDraftListHelper.getSignature(page) !== this.declinedSignature) {
10413
+ this.showAlert();
10414
+ }
10415
+ }
10416
+ /**
10417
+ * Обновляет на месте краткие данные черновиков и перезапрашивает полные данные раскрытых черновиков, состояние которых изменилось.
10418
+ * Перезапрос раскрытого черновика не повторяется, пока выполняется предыдущий. Ошибка перезапроса не показывается:
10419
+ * это фоновое обновление, его повторит следующий опрос.
10420
+ *
10421
+ * @param drafts Краткие данные черновиков.
10422
+ */
10423
+ applyDrafts(drafts) {
10424
+ const items = this.pageItems();
10425
+ if (!items) {
10426
+ return;
10427
+ }
10428
+ ScDraftListHelper.getChangedFullDraftIds(items, drafts)
10429
+ .filter((draftId) => !this.refreshingIds.has(draftId))
10430
+ .forEach((draftId) => {
10431
+ this.refreshingIds.add(draftId);
10432
+ this.draftsService
10433
+ .fetch$(draftId)
10434
+ .pipe(catchError(() => EMPTY), finalize(() => {
10435
+ this.refreshingIds.delete(draftId);
10436
+ }), takeUntilDestroyed(this.destroyRef))
10437
+ .subscribe();
10438
+ });
10439
+ this.pageItems.set(ScDraftListHelper.merge(items, drafts));
10440
+ }
10441
+ /**
10442
+ * Запоминает черновики, которых не оказалось в ответе опроса, чтобы больше их не опрашивать.
10443
+ *
10444
+ * @param ids Номера опрошенных черновиков.
10445
+ * @param drafts Полученные краткие данные черновиков.
10446
+ */
10447
+ markMissing(ids, drafts) {
10448
+ const missing = ids.filter((id) => !drafts.some((draft) => draft.id === id));
10449
+ if (missing.length > 0) {
10450
+ this.missingIds.update((current) => new Set([...current, ...missing]));
10451
+ }
10452
+ }
10453
+ /**
10454
+ * Показывает уведомление об изменении списка, если оно ещё не показано. Ответ «Нет» и закрытие уведомления считаются отказом
10455
+ * от перезагрузки отложенного состава.
10456
+ */
10457
+ showAlert() {
10458
+ if (this.alertSubscription) {
10459
+ return;
10460
+ }
10461
+ this.alertSubscription = this.alerts
10462
+ .open(new PolymorpheusComponent(ScConfirmAlertComponent), {
10463
+ label: 'Список черновиков изменился',
10464
+ data: { content: 'Перезагрузить список?' },
10465
+ appearance: 'info',
10466
+ autoClose: 0,
10467
+ })
10468
+ .pipe(defaultIfEmpty(false), finalize(() => {
10469
+ this.alertSubscription = null;
10470
+ }))
10471
+ .subscribe((isConfirmed) => {
10472
+ if (isConfirmed) {
10473
+ this.applyPendingPage();
10474
+ }
10475
+ else {
10476
+ this.declinePendingPage();
10477
+ }
10478
+ });
10479
+ }
10480
+ /**
10481
+ * Закрывает показанное уведомление об изменении списка.
10482
+ */
10483
+ dismissAlert() {
10484
+ this.alertSubscription?.unsubscribe();
10485
+ this.alertSubscription = null;
10486
+ }
10487
+ /**
10488
+ * Показывает отложенную страницу с сохранением раскрытых строк. Уведомление закрывается всякий раз, когда отложенная страница
10489
+ * сбрасывается, поэтому при согласии пользователя она всегда есть.
10490
+ */
10491
+ applyPendingPage() {
10492
+ const page = this.pendingPage;
10493
+ if (!page) {
10494
+ return;
10495
+ }
10496
+ this.pendingPage = null;
10497
+ this.missingIds.set(new Set());
10498
+ this.pageItems.set(ScDraftListHelper.fromPage(this.pageItems() ?? [], page.data));
10499
+ this.pageMeta.set(page.meta);
10500
+ }
10501
+ /**
10502
+ * Запоминает отказ от перезагрузки отложенного состава: уведомление о нём больше не показывается, пока состав снова не изменится.
10503
+ */
10504
+ declinePendingPage() {
10505
+ this.declinedSignature = this.pendingPage ? ScDraftListHelper.getSignature(this.pendingPage) : null;
10506
+ }
10507
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScDraftListUpdatesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
10508
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScDraftListUpdatesService }); }
10509
+ }
10510
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScDraftListUpdatesService, decorators: [{
10511
+ type: Injectable
10512
+ }], ctorParameters: () => [] });
10513
+
10514
+ /**
10515
+ * Перечисление состояний черновика Профстрой для отображения пользователю.
10516
+ */
10517
+ // eslint-disable-next-line no-shadow
10518
+ var ScProfstroyDraftState;
10519
+ (function (ScProfstroyDraftState) {
10520
+ /**
10521
+ * Выполняется обработка.
10522
+ */
10523
+ ScProfstroyDraftState["processing"] = "processing";
10524
+ /**
10525
+ * Актуализирован, срок действия не истёк.
10526
+ */
10527
+ ScProfstroyDraftState["actual"] = "actual";
10528
+ /**
10529
+ * Актуализирован, но срок действия истёк.
10530
+ */
10531
+ ScProfstroyDraftState["outdated"] = "outdated";
10532
+ /**
10533
+ * Обработка завершилась с ошибкой.
10534
+ */
10535
+ ScProfstroyDraftState["error"] = "error";
10536
+ /**
10537
+ * Требуется актуализация после изменения состава.
10538
+ */
10539
+ ScProfstroyDraftState["needsActualization"] = "needsActualization";
10540
+ /**
10541
+ * Состояние не известно.
10542
+ */
10543
+ ScProfstroyDraftState["unknown"] = "unknown";
10544
+ })(ScProfstroyDraftState || (ScProfstroyDraftState = {}));
10545
+
10546
+ /**
10547
+ * Класс-помощник для отображения черновиков Профстрой.
10548
+ */
10549
+ class ScProfstroyDraftHelper {
10550
+ /**
10551
+ * Данные об отображении состояний черновика.
10552
+ */
10553
+ static { this.stateViews = {
10554
+ [ScProfstroyDraftState.processing]: {
10555
+ label: 'Выполняется обработка',
10556
+ appearance: 'info',
10557
+ description: 'Файл обрабатывается, позиции и цены уточняются. Изменение состава и оформление заказа станут доступны после завершения.',
10558
+ },
10559
+ [ScProfstroyDraftState.actual]: {
10560
+ label: 'Актуализирован',
10561
+ appearance: 'positive',
10562
+ description: 'Цены и позиции актуальны, заказ можно оформлять.',
10563
+ },
10564
+ [ScProfstroyDraftState.outdated]: {
10565
+ label: 'Данные устарели',
10566
+ appearance: 'warning',
10567
+ description: 'С момента последней актуализации прошло много времени. Запустите её повторно, чтобы оформить заказ по актуальным ценам.',
10568
+ },
10569
+ [ScProfstroyDraftState.error]: {
10570
+ label: 'Ошибка обработки',
10571
+ appearance: 'negative',
10572
+ description: 'Обработка завершилась с ошибкой. Запустите актуализацию повторно или соберите заявку из файла заново.',
10573
+ },
10574
+ [ScProfstroyDraftState.needsActualization]: {
10575
+ label: 'Требуется актуализация',
10576
+ appearance: 'warning',
10577
+ description: 'Состав заявки изменён после последней актуализации. Запустите актуализацию, чтобы обновить цены и оформить заказ. Цены и примечания к позициям обновятся после актуализации.',
10578
+ },
10579
+ [ScProfstroyDraftState.unknown]: {
10580
+ label: 'Состояние неизвестно',
10581
+ appearance: 'neutral',
10582
+ description: '',
10583
+ },
10584
+ }; }
10585
+ /**
10586
+ * Возвращает состояние черновика на указанный момент.
10587
+ *
10588
+ * @param actualization Данные об актуализации.
10589
+ * @param now Момент проверки.
10590
+ */
10591
+ static getState(actualization, now) {
10592
+ switch (actualization?.status) {
10593
+ case ScOrderDraftActualizationStatus.processing:
10594
+ return ScProfstroyDraftState.processing;
10595
+ case ScOrderDraftActualizationStatus.completed:
10596
+ return actualization.isOutdatedAt(now) ? ScProfstroyDraftState.outdated : ScProfstroyDraftState.actual;
10597
+ case ScOrderDraftActualizationStatus.error:
10598
+ return ScProfstroyDraftState.error;
10599
+ case ScOrderDraftActualizationStatus.needsActualization:
10600
+ return ScProfstroyDraftState.needsActualization;
10601
+ default:
10602
+ return ScProfstroyDraftState.unknown;
10603
+ }
10604
+ }
10605
+ /**
10606
+ * Возвращает данные об отображении состояния черновика на указанный момент.
10607
+ *
10608
+ * @param actualization Данные об актуализации.
10609
+ * @param now Момент проверки.
10610
+ */
10611
+ static getStateView(actualization, now) {
10612
+ return ScProfstroyDraftHelper.stateViews[ScProfstroyDraftHelper.getState(actualization, now)];
10613
+ }
10614
+ /**
10615
+ * Возвращает данные об отображении панели состояния черновика: для актуальной заявки пояснение зависит от того, сколько позиций попадёт в заказ.
10616
+ *
10617
+ * @param draft Черновик Профстрой.
10618
+ * @param now Момент проверки.
10619
+ */
10620
+ static getPanelView(draft, now) {
10621
+ const view = ScProfstroyDraftHelper.getStateView(draft.actualization, now);
10622
+ if (ScProfstroyDraftHelper.getState(draft.actualization, now) !== ScProfstroyDraftState.actual) {
10623
+ return view;
10624
+ }
10625
+ const total = draft.products.length;
10626
+ const orderable = draft.products.filter((item) => item.isOrderable).length;
10627
+ if (orderable === 0) {
10628
+ return {
10629
+ ...view,
10630
+ appearance: 'warning',
10631
+ description: 'Актуализация завершена, но ни одна позиция не попадёт в заказ. Исправьте или удалите позиции с примечанием и запустите актуализацию повторно или соберите заявку заново из файла.',
10632
+ };
10633
+ }
10634
+ return orderable < total ? { ...view, description: `Цены актуальны. В заказ попадут ${orderable} из ${total} позиций, остальные отмечены в поле «Примечание».` } : view;
10635
+ }
10636
+ }
10637
+
10638
+ /**
10639
+ * Компонент панели состояния актуализации черновика Профстрой с действиями актуализации и пересборки.
10640
+ */
10641
+ class ScProfstroyActualizationPanelComponent {
10642
+ constructor() {
10643
+ /**
10644
+ * Список ссылок приложения.
10645
+ */
10646
+ this.urls = inject(SC_URLS);
10647
+ /**
10648
+ * Объект документа.
10649
+ */
10650
+ this.document = inject(DOCUMENT);
10651
+ /**
10652
+ * Черновик Профстрой.
10653
+ */
10654
+ this.draft = input.required();
10655
+ /**
10656
+ * Момент, на который определяется состояние. Передаётся снаружи и обновляется по таймеру проверки срока, иначе метка «Данные устарели» не сменится.
10657
+ */
10658
+ this.now = input.required();
10659
+ /**
10660
+ * Признак, что действия над черновиком недоступны.
10661
+ */
10662
+ this.busy = input(false);
10663
+ /**
10664
+ * Событие запуска актуализации.
10665
+ */
10666
+ this.actualize = output();
10667
+ /**
10668
+ * Событие запуска пересборки из файла.
10669
+ */
10670
+ this.reload = output();
10671
+ /**
10672
+ * Текущее состояние черновика.
10673
+ */
10674
+ this.state = computed(() => ScProfstroyDraftHelper.getState(this.draft().actualization, this.now()));
10675
+ /**
10676
+ * Данные об отображении состояния.
10677
+ */
10678
+ this.stateView = computed(() => ScProfstroyDraftHelper.getPanelView(this.draft(), this.now()));
10679
+ /**
10680
+ * Признак, что выполняется обработка.
10681
+ */
10682
+ this.isProcessing = computed(() => this.state() === ScProfstroyDraftState.processing);
10683
+ /**
10684
+ * Признак, что срок действия актуализации ещё не истёк.
10685
+ */
10686
+ this.isActual = computed(() => this.state() === ScProfstroyDraftState.actual);
10687
+ /**
10688
+ * Абсолютная ссылка на исходный файл заявки. Файл лежит на хосте API, адрес API может быть указан относительно сайта.
10689
+ */
10690
+ this.fileUrl = computed(() => {
10691
+ const url = this.draft().file?.url;
10692
+ return url ? new URL(url, new URL(this.urls.apiUrl, this.document.baseURI)).href : null;
10693
+ });
10694
+ }
10695
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScProfstroyActualizationPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10696
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScProfstroyActualizationPanelComponent, isStandalone: true, selector: "sc-profstroy-actualization-panel", inputs: { draft: { classPropertyName: "draft", publicName: "draft", isSignal: true, isRequired: true, transformFunction: null }, now: { classPropertyName: "now", publicName: "now", isSignal: true, isRequired: true, transformFunction: null }, busy: { classPropertyName: "busy", publicName: "busy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { actualize: "actualize", reload: "reload" }, ngImport: i0, template: "@let actualization = draft().actualization;\n@let view = stateView();\n\n<tui-notification\n [appearance]=\"view.appearance\"\n size=\"m\"\n>\n <div class=\"flex flex-col items-start gap-2 text-start\">\n <div class=\"flex items-center justify-start gap-2 text-start font-bold\">\n @if (isProcessing()) {\n <tui-loader size=\"s\" />\n }\n {{ view.label }}\n </div>\n\n <div class=\"flex flex-col items-start gap-1\">\n @if (actualization?.actualizedAt; as actualizedAt) {\n <span>\u0410\u043A\u0442\u0443\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D {{ actualizedAt | scFormatDate }}</span>\n }\n @if (isActual() && actualization?.expiresAt; as expiresAt) {\n <span>\u0410\u043A\u0442\u0443\u0430\u043B\u0435\u043D \u0434\u043E {{ expiresAt | scFormatDate }}</span>\n }\n @if (view.description) {\n <span>{{ view.description }}</span>\n }\n @if (actualization?.error; as error) {\n <span class=\"font-bold\">{{ error }}</span>\n }\n @if (fileUrl(); as url) {\n <span>\n \u0424\u0430\u0439\u043B \u0437\u0430\u044F\u0432\u043A\u0438:\n <a\n tuiLink\n [href]=\"url\"\n target=\"_blank\"\n rel=\"noopener\"\n class=\"break-all\"\n >\n {{ draft().file?.originalName ?? '\u0441\u043A\u0430\u0447\u0430\u0442\u044C' }}\n </a>\n </span>\n }\n </div>\n\n <div class=\"flex flex-wrap gap-2\">\n <button\n tuiButton\n type=\"button\"\n size=\"s\"\n appearance=\"secondary\"\n iconStart=\"@tui.refresh-cw\"\n [disabled]=\"isProcessing() || busy()\"\n (click)=\"actualize.emit()\"\n >\n \u0410\u043A\u0442\u0443\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C\n </button>\n <button\n tuiButton\n type=\"button\"\n size=\"s\"\n appearance=\"secondary\"\n iconStart=\"@tui.file-up\"\n [disabled]=\"isProcessing() || busy()\"\n (click)=\"reload.emit()\"\n >\n \u0421\u043E\u0431\u0440\u0430\u0442\u044C \u0437\u0430\u043D\u043E\u0432\u043E \u0438\u0437 \u0444\u0430\u0439\u043B\u0430\n </button>\n </div>\n </div>\n</tui-notification>\n", dependencies: [{ kind: "directive", type: TuiNotification, selector: "tui-notification,a[tuiNotification],button[tuiNotification]", inputs: ["appearance", "icon", "size"] }, { kind: "component", type: TuiLoader, selector: "tui-loader", inputs: ["size", "inheritColor", "overlay", "textContent", "showLoader"] }, { kind: "directive", type: TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }, { kind: "directive", type: TuiLink, selector: "a[tuiLink], button[tuiLink]", inputs: ["pseudo"] }, { kind: "pipe", type: ScFormatDatePipe, name: "scFormatDate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10697
+ }
10698
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScProfstroyActualizationPanelComponent, decorators: [{
10699
+ type: Component,
10700
+ args: [{ standalone: true, selector: 'sc-profstroy-actualization-panel', imports: [TuiNotification, TuiLoader, TuiButton, TuiLink, ScFormatDatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let actualization = draft().actualization;\n@let view = stateView();\n\n<tui-notification\n [appearance]=\"view.appearance\"\n size=\"m\"\n>\n <div class=\"flex flex-col items-start gap-2 text-start\">\n <div class=\"flex items-center justify-start gap-2 text-start font-bold\">\n @if (isProcessing()) {\n <tui-loader size=\"s\" />\n }\n {{ view.label }}\n </div>\n\n <div class=\"flex flex-col items-start gap-1\">\n @if (actualization?.actualizedAt; as actualizedAt) {\n <span>\u0410\u043A\u0442\u0443\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D {{ actualizedAt | scFormatDate }}</span>\n }\n @if (isActual() && actualization?.expiresAt; as expiresAt) {\n <span>\u0410\u043A\u0442\u0443\u0430\u043B\u0435\u043D \u0434\u043E {{ expiresAt | scFormatDate }}</span>\n }\n @if (view.description) {\n <span>{{ view.description }}</span>\n }\n @if (actualization?.error; as error) {\n <span class=\"font-bold\">{{ error }}</span>\n }\n @if (fileUrl(); as url) {\n <span>\n \u0424\u0430\u0439\u043B \u0437\u0430\u044F\u0432\u043A\u0438:\n <a\n tuiLink\n [href]=\"url\"\n target=\"_blank\"\n rel=\"noopener\"\n class=\"break-all\"\n >\n {{ draft().file?.originalName ?? '\u0441\u043A\u0430\u0447\u0430\u0442\u044C' }}\n </a>\n </span>\n }\n </div>\n\n <div class=\"flex flex-wrap gap-2\">\n <button\n tuiButton\n type=\"button\"\n size=\"s\"\n appearance=\"secondary\"\n iconStart=\"@tui.refresh-cw\"\n [disabled]=\"isProcessing() || busy()\"\n (click)=\"actualize.emit()\"\n >\n \u0410\u043A\u0442\u0443\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C\n </button>\n <button\n tuiButton\n type=\"button\"\n size=\"s\"\n appearance=\"secondary\"\n iconStart=\"@tui.file-up\"\n [disabled]=\"isProcessing() || busy()\"\n (click)=\"reload.emit()\"\n >\n \u0421\u043E\u0431\u0440\u0430\u0442\u044C \u0437\u0430\u043D\u043E\u0432\u043E \u0438\u0437 \u0444\u0430\u0439\u043B\u0430\n </button>\n </div>\n </div>\n</tui-notification>\n" }]
10701
+ }] });
10702
+
10703
+ /**
10704
+ * Компонент метки «Профстрой» у черновика или заказа, созданного из файла программы «Профстрой».
10705
+ */
10706
+ class ScProfstroyBadgeComponent {
10707
+ constructor() {
10708
+ /**
10709
+ * Размер метки.
10710
+ */
10711
+ this.size = input('m');
10712
+ }
10713
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScProfstroyBadgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10714
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.2.14", type: ScProfstroyBadgeComponent, isStandalone: true, selector: "sc-profstroy-badge", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<tui-badge\n class=\"!font-medium\"\n appearance=\"primary\"\n [size]=\"size()\"\n>\n \u041F\u0440\u043E\u0444\u0441\u0442\u0440\u043E\u0439\n</tui-badge>\n", dependencies: [{ kind: "directive", type: TuiBadge, selector: "tui-badge,[tuiBadge]", inputs: ["size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10715
+ }
10716
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScProfstroyBadgeComponent, decorators: [{
10717
+ type: Component,
10718
+ args: [{ standalone: true, selector: 'sc-profstroy-badge', imports: [TuiBadge], changeDetection: ChangeDetectionStrategy.OnPush, template: "<tui-badge\n class=\"!font-medium\"\n appearance=\"primary\"\n [size]=\"size()\"\n>\n \u041F\u0440\u043E\u0444\u0441\u0442\u0440\u043E\u0439\n</tui-badge>\n" }]
10719
+ }] });
10720
+
10721
+ /**
10722
+ * Компонент меток черновика Профстрой: признак и текущее состояние обработки.
10723
+ */
10724
+ class ScProfstroyDraftBadgesComponent {
10725
+ constructor() {
10726
+ /**
10727
+ * Данные об актуализации черновика.
10728
+ */
10729
+ this.actualization = input();
10730
+ /**
10731
+ * Момент, на который определяется состояние. Передаётся снаружи и обновляется по таймеру проверки срока, иначе метка «Данные устарели» не сменится.
10732
+ */
10733
+ this.now = input.required();
10734
+ /**
10735
+ * Данные об отображении состояния.
10736
+ */
10737
+ this.stateView = computed(() => ScProfstroyDraftHelper.getStateView(this.actualization(), this.now()));
10738
+ }
10739
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScProfstroyDraftBadgesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10740
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.2.14", type: ScProfstroyDraftBadgesComponent, isStandalone: true, selector: "sc-profstroy-draft-badges", inputs: { actualization: { classPropertyName: "actualization", publicName: "actualization", isSignal: true, isRequired: false, transformFunction: null }, now: { classPropertyName: "now", publicName: "now", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<span class=\"inline-flex flex-wrap items-center gap-1\">\n <sc-profstroy-badge />\n <tui-badge\n class=\"!font-medium\"\n [appearance]=\"stateView().appearance\"\n size=\"m\"\n >\n {{ stateView().label }}\n </tui-badge>\n</span>\n", dependencies: [{ kind: "directive", type: TuiBadge, selector: "tui-badge,[tuiBadge]", inputs: ["size"] }, { kind: "component", type: ScProfstroyBadgeComponent, selector: "sc-profstroy-badge", inputs: ["size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10741
+ }
10742
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScProfstroyDraftBadgesComponent, decorators: [{
10743
+ type: Component,
10744
+ args: [{ standalone: true, selector: 'sc-profstroy-draft-badges', imports: [TuiBadge, ScProfstroyBadgeComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<span class=\"inline-flex flex-wrap items-center gap-1\">\n <sc-profstroy-badge />\n <tui-badge\n class=\"!font-medium\"\n [appearance]=\"stateView().appearance\"\n size=\"m\"\n >\n {{ stateView().label }}\n </tui-badge>\n</span>\n" }]
10745
+ }] });
10746
+
10747
+ /**
10748
+ * Класс-помощник для сообщений об ошибках запросов к API.
10749
+ */
10750
+ class ScApiErrorHelper {
10751
+ /**
10752
+ * Возвращает текст ошибки из ответа API или указанный текст, если ответ его не содержит.
10753
+ *
10754
+ * @param error Ошибка запроса.
10755
+ * @param fallback Текст по умолчанию.
10756
+ */
10757
+ static getMessage(error, fallback) {
10758
+ const message = error instanceof HttpErrorResponse ? error.error?.message : undefined;
10759
+ return message?.trim() ? message : fallback;
10760
+ }
10761
+ /**
10762
+ * Возвращает признак ответа 404: запись не найдена, например удалена в другой вкладке.
10763
+ *
10764
+ * @param error Ошибка запроса.
10765
+ */
10766
+ static isNotFound(error) {
10767
+ return error instanceof HttpErrorResponse && error.status === HttpStatusCode.NotFound;
10768
+ }
10769
+ }
10770
+
10771
+ /* eslint-disable @typescript-eslint/unbound-method */
10772
+ /**
10773
+ * Компонент диалога создания черновика заказа из файла программы «Профстрой».
10774
+ */
10775
+ class ScProfstroyDraftCreateDialogComponent {
10776
+ constructor() {
10777
+ /**
10778
+ * Контекст диалогового окна, в котором открыт компонент.
10779
+ */
10780
+ this.context = inject(POLYMORPHEUS_CONTEXT);
10781
+ /**
10782
+ * Сервис для работы с черновиками заказов.
10783
+ */
10784
+ this.draftsService = inject(ScOrderDraftsService);
10785
+ /**
10786
+ * Ссылка для автоматического управления уничтожением зависимостей.
10787
+ */
10788
+ this.destroyRef = inject(DestroyRef);
10789
+ /**
10790
+ * Настройки поля выбора файлов, в том числе наибольший размер файла из настроек приложения.
9816
10791
  */
9817
- this.editable = input(false);
10792
+ this.inputFilesOptions = inject(TUI_INPUT_FILES_OPTIONS);
9818
10793
  /**
9819
- * Заголовок для секций «под заказ» и «нет на складе». Если не указан — заголовок не выводится.
10794
+ * Допустимое расширение файла.
9820
10795
  */
9821
- this.sectionHeader = input();
10796
+ this.accept = '.xlsx';
9822
10797
  /**
9823
- * Выходные данные при изменении выбора позиций.
10798
+ * Наибольшая длина названия заявки.
9824
10799
  */
9825
- this.selectedItemsIds = model([]);
10800
+ this.nameMaxLength = 255;
9826
10801
  /**
9827
- * Позиции в наличии на выбранном складе (не под заказ).
10802
+ * Форма создания черновика. Формат и размер файла проверяются на самом поле: после выбора файла поле выбора убирается вместе со своими проверками.
9828
10803
  */
9829
- this.itemsInWarehouseStock = computed(() => {
9830
- const items = this.items();
9831
- const warehouseId = this.warehouseId();
9832
- return items.filter((item) => item.product.isWarehouseStockExist(warehouseId) && !item.product.onOrder);
10804
+ this.form = new FormGroup({
10805
+ file: new FormControl(null, [Validators.required, tuiCreateFileFormatValidator(this.accept), tuiCreateFileSizeValidator(this.inputFilesOptions.maxFileSize)]),
10806
+ name: new FormControl('', { nonNullable: true, validators: Validators.maxLength(this.nameMaxLength) }),
9833
10807
  });
9834
10808
  /**
9835
- * Позиции под заказ.
10809
+ * Признак выполнения запроса создания.
9836
10810
  */
9837
- this.itemsOnOrder = computed(() => {
9838
- return this.items().filter((item) => item.product.onOrder);
9839
- });
10811
+ this.submitting = signal(false);
9840
10812
  /**
9841
- * Позиции, которых нет на выбранном складе (не под заказ).
10813
+ * Признак попытки создать заявку. До неё незаполненное поле файла ошибкой не подсвечивается: поле теряет фокус уже при открытии окна выбора файла.
9842
10814
  */
9843
- this.itemsNotInWarehouseStock = computed(() => {
9844
- const items = this.items();
9845
- const warehouseId = this.warehouseId();
9846
- return items.filter((item) => !item.product.isWarehouseStockExist(warehouseId) && !item.product.onOrder);
10815
+ this.submitAttempted = signal(false);
10816
+ }
10817
+ /**
10818
+ * Удаляет выбранный файл.
10819
+ */
10820
+ removeFile() {
10821
+ this.form.controls.file.setValue(null);
10822
+ }
10823
+ /**
10824
+ * Отправляет файл и создаёт черновик. Ошибки сервера с прошлой отправки не мешают отправить форму снова.
10825
+ */
10826
+ submit() {
10827
+ this.submitAttempted.set(true);
10828
+ Object.values(this.form.controls).forEach((control) => {
10829
+ control.updateValueAndValidity();
10830
+ });
10831
+ this.form.markAllAsTouched();
10832
+ const file = this.form.controls.file.value;
10833
+ if (this.form.invalid || !file || this.submitting()) {
10834
+ return;
10835
+ }
10836
+ this.submitting.set(true);
10837
+ ScFileHelper.readAsUploadedFile$(file)
10838
+ .pipe(switchMap((uploadedFile) => this.draftsService.createProfstroy$({ file: uploadedFile, name: this.form.controls.name.value.trim() || undefined })), finalize(() => {
10839
+ this.submitting.set(false);
10840
+ }), takeUntilDestroyed(this.destroyRef))
10841
+ .subscribe({
10842
+ next: (draft) => {
10843
+ this.context.completeWith(draft);
10844
+ },
10845
+ error: (error) => {
10846
+ this.applyError(error);
10847
+ },
9847
10848
  });
9848
10849
  }
9849
10850
  /**
9850
- * Обработчик изменения выбора позиций.
10851
+ * Показывает ошибку создания в форме: ошибки проверки данных у полей, ошибку чтения файла у поля файла, остальные ошибкой формы (401 обрабатывает авторизация).
10852
+ * Диалог остаётся открытым.
9851
10853
  *
9852
- * @param itemsIds Идентификаторы выбранных позиций.
9853
- * @param items Позиции.
10854
+ * @param error Ошибка чтения файла или запроса.
9854
10855
  */
9855
- selectedItemsHandler(itemsIds, items) {
9856
- this.selectedItemsIds.update((previous) => [...previous.filter((id) => !items.some((item) => item.id === id) && this.items().some((item) => item.id === id)), ...itemsIds]);
10856
+ applyError(error) {
10857
+ if (!(error instanceof HttpErrorResponse)) {
10858
+ this.form.controls.file.setErrors({ serverResponse: new TuiValidationError('Не удалось прочитать файл.') });
10859
+ return;
10860
+ }
10861
+ if (error.status === HttpStatusCode.Unauthorized) {
10862
+ return;
10863
+ }
10864
+ const { errors } = (error.error ?? {});
10865
+ if (error.status !== HttpStatusCode.UnprocessableEntity || !errors) {
10866
+ this.form.setErrors({ serverResponse: new TuiValidationError(ScApiErrorHelper.getMessage(error, 'Не удалось создать заявку.')) });
10867
+ return;
10868
+ }
10869
+ Object.entries(errors).forEach(([key, messages]) => {
10870
+ const control = key === 'name' ? this.form.controls.name : this.form.controls.file;
10871
+ control.setErrors({ serverResponse: new TuiValidationError(messages.join(' ')) });
10872
+ control.markAsTouched();
10873
+ });
9857
10874
  }
9858
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScOrderItemsListByStockComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9859
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScOrderItemsListByStockComponent, isStandalone: true, selector: "sc-order-items-list-by-stock", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, warehouseId: { classPropertyName: "warehouseId", publicName: "warehouseId", isSignal: true, isRequired: true, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, sectionHeader: { classPropertyName: "sectionHeader", publicName: "sectionHeader", isSignal: true, isRequired: false, transformFunction: null }, selectedItemsIds: { classPropertyName: "selectedItemsIds", publicName: "selectedItemsIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedItemsIds: "selectedItemsIdsChange" }, ngImport: i0, template: "<div class=\"flex flex-col gap-5\">\n @if (itemsInWarehouseStock().length > 0) {\n <sc-order-items-list\n [items]=\"itemsInWarehouseStock()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n (selectedItemsIdsChange)=\"selectedItemsHandler($event, itemsInWarehouseStock())\"\n />\n }\n\n @if (itemsOnOrder().length > 0) {\n <div class=\"flex flex-col gap-2.5\">\n @if (sectionHeader()) {\n <div class=\"text-base font-bold text-sc-dark-grey\">{{ sectionHeader() }}</div>\n }\n <sc-order-items-list\n [items]=\"itemsOnOrder()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n (selectedItemsIdsChange)=\"selectedItemsHandler($event, itemsOnOrder())\"\n />\n </div>\n }\n\n @if (itemsNotInWarehouseStock().length > 0) {\n <div class=\"flex flex-col gap-2.5\">\n @if (sectionHeader()) {\n <div class=\"text-base font-bold text-sc-dark-grey\">{{ sectionHeader() }}</div>\n }\n <sc-order-items-list\n [items]=\"itemsNotInWarehouseStock()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n (selectedItemsIdsChange)=\"selectedItemsHandler($event, itemsNotInWarehouseStock())\"\n />\n </div>\n }\n</div>\n", dependencies: [{ kind: "component", type: ScOrderItemsListComponent, selector: "sc-order-items-list", inputs: ["selectable", "editable", "items"], outputs: ["selectedItemsIdsChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10875
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScProfstroyDraftCreateDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10876
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScProfstroyDraftCreateDialogComponent, isStandalone: true, selector: "sc-profstroy-draft-create-dialog", providers: [
10877
+ {
10878
+ provide: TUI_INPUT_FILE_TEXTS,
10879
+ useFactory: () => inject(TUI_INPUT_FILE_TEXTS, { skipSelf: true }).pipe(map((texts) => ({ ...texts, formatRejectionReason: 'Файл должен быть в формате .xlsx' }))),
10880
+ },
10881
+ ], ngImport: i0, template: "<form\n [formGroup]=\"form\"\n class=\"flex flex-col gap-5\"\n (ngSubmit)=\"submit()\"\n>\n <!-- \u0424\u0430\u0439\u043B. \u041F\u0440\u0438\u0447\u0438\u043D\u0443 \u043E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u0438\u044F \u0444\u0430\u0439\u043B\u0430 \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u0441\u0442\u0440\u043E\u043A\u0430 \u0444\u0430\u0439\u043B\u0430, \u043F\u043E\u0434 \u043F\u043E\u043B\u0435\u043C \u043E\u043D\u0430 \u043D\u0435 \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u0435\u0442\u0441\u044F. \u041D\u0435\u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D\u043D\u043E\u0435 \u043F\u043E\u043B\u0435 \u043F\u043E\u0434\u0441\u0432\u0435\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044F \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0441\u043B\u0435 \u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u0441\u043E\u0437\u0434\u0430\u0442\u044C \u0437\u0430\u044F\u0432\u043A\u0443. -->\n <div class=\"flex flex-col gap-1\">\n @let file = form.controls.file.value;\n @let rejected = file | tuiFileRejected: { accept } | async;\n @if (!file) {\n <label tuiInputFiles>\n <input\n tuiInputFiles\n formControlName=\"file\"\n [accept]=\"accept\"\n [invalid]=\"submitAttempted() ? null : false\"\n />\n </label>\n }\n <tui-files>\n @if (file) {\n <tui-file\n [state]=\"rejected ? 'error' : 'normal'\"\n [file]=\"rejected ?? file\"\n (remove)=\"removeFile()\"\n />\n }\n </tui-files>\n <span class=\"text-body-s text-tui-text-02\">\u0424\u0430\u0439\u043B Excel, \u0432\u044B\u0433\u0440\u0443\u0436\u0435\u043D\u043D\u044B\u0439 \u0438\u0437 \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u044B \u00AB\u041F\u0440\u043E\u0444\u0441\u0442\u0440\u043E\u0439\u00BB: {{ accept }}</span>\n @if (!rejected) {\n @let fileErrorVisible = submitAttempted() || !form.controls.file.hasError('required');\n <tui-error\n formControlName=\"file\"\n [error]=\"fileErrorVisible ? (['required', 'serverResponse'] | tuiFieldError | async) : null\"\n />\n }\n </div>\n\n <!-- \u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0437\u0430\u044F\u0432\u043A\u0438. -->\n <label tuiLabel>\n \u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0437\u0430\u044F\u0432\u043A\u0438\n <tui-textfield>\n <input\n tuiTextfield\n formControlName=\"name\"\n placeholder=\"\u041D\u0430\u043F\u0440\u0438\u043C\u0435\u0440: \u0411\u0430\u043B\u043A\u043E\u043D\u044B, \u043E\u0431\u044A\u0435\u043A\u0442 \u043D\u0430 \u041B\u0435\u043D\u0438\u043D\u0430\"\n autocomplete=\"off\"\n [maxLength]=\"nameMaxLength\"\n />\n </tui-textfield>\n <tui-error\n formControlName=\"name\"\n [error]=\"[] | tuiFieldError | async\"\n />\n </label>\n\n @if (form.errors?.['serverResponse']) {\n <tui-error [error]=\"[] | tuiFieldError | async\" />\n }\n\n <tui-notification\n appearance=\"info\"\n size=\"m\"\n >\n \u041E\u0431\u0440\u0430\u0431\u043E\u0442\u043A\u0430 \u0444\u0430\u0439\u043B\u0430 \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u0435\u0442\u0441\u044F \u0432 \u0444\u043E\u043D\u0435: \u043F\u043E\u0437\u0438\u0446\u0438\u0438 \u0438 \u0446\u0435\u043D\u044B \u043F\u043E\u044F\u0432\u044F\u0442\u0441\u044F \u0432 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u0435 \u0447\u0435\u0440\u0435\u0437 \u043D\u0435\u043A\u043E\u0442\u043E\u0440\u043E\u0435 \u0432\u0440\u0435\u043C\u044F.\n </tui-notification>\n\n <div class=\"flex flex-wrap gap-2\">\n <button\n tuiButton\n type=\"submit\"\n iconStart=\"@tui.sc.send\"\n [loading]=\"submitting()\"\n >\n \u0421\u043E\u0437\u0434\u0430\u0442\u044C \u0437\u0430\u044F\u0432\u043A\u0443\n </button>\n <button\n tuiButton\n type=\"button\"\n appearance=\"secondary\"\n [disabled]=\"submitting()\"\n (click)=\"context.$implicit.complete()\"\n >\n \u041E\u0442\u043C\u0435\u043D\u0430\n </button>\n </div>\n</form>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i2$1.TuiFile, selector: "tui-file,a[tuiFile],button[tuiFile]", inputs: ["file", "state", "size", "showDelete", "showSize", "leftContent"], outputs: ["remove"] }, { kind: "component", type: i2$1.TuiInputFiles, selector: "label[tuiInputFiles]" }, { kind: "component", type: i2$1.TuiFilesComponent, selector: "tui-files", inputs: ["max", "expanded"], outputs: ["expandedChange"] }, { kind: "pipe", type: i2$1.TuiFileRejectedPipe, name: "tuiFileRejected" }, { kind: "directive", type: i2$1.TuiInputFilesDirective, selector: "input[tuiInputFiles]", outputs: ["reject"] }, { kind: "directive", type: TuiLabel, selector: "label[tuiLabel]" }, { kind: "component", type: TuiError, selector: "tui-error", inputs: ["error"] }, { kind: "pipe", type: TuiFieldErrorPipe, name: "tuiFieldError" }, { kind: "component", type: i1$2.TuiTextfieldComponent, selector: "tui-textfield:not([multi])" }, { kind: "directive", type: i1$2.TuiTextfieldDirective, selector: "input[tuiTextfield]:not([tuiInputCard]):not([tuiInputExpire]):not([tuiInputCVC])" }, { kind: "directive", type: TuiNotification, selector: "tui-notification,a[tuiNotification],button[tuiNotification]", inputs: ["appearance", "icon", "size"] }, { kind: "directive", type: TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }, { kind: "component", type: TuiButtonLoading, selector: "[tuiButton][loading],[tuiIconButton][loading]", inputs: ["size", "loading"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9860
10882
  }
9861
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScOrderItemsListByStockComponent, decorators: [{
10883
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScProfstroyDraftCreateDialogComponent, decorators: [{
9862
10884
  type: Component,
9863
- args: [{ standalone: true, selector: 'sc-order-items-list-by-stock', imports: [ScOrderItemsListComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex flex-col gap-5\">\n @if (itemsInWarehouseStock().length > 0) {\n <sc-order-items-list\n [items]=\"itemsInWarehouseStock()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n (selectedItemsIdsChange)=\"selectedItemsHandler($event, itemsInWarehouseStock())\"\n />\n }\n\n @if (itemsOnOrder().length > 0) {\n <div class=\"flex flex-col gap-2.5\">\n @if (sectionHeader()) {\n <div class=\"text-base font-bold text-sc-dark-grey\">{{ sectionHeader() }}</div>\n }\n <sc-order-items-list\n [items]=\"itemsOnOrder()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n (selectedItemsIdsChange)=\"selectedItemsHandler($event, itemsOnOrder())\"\n />\n </div>\n }\n\n @if (itemsNotInWarehouseStock().length > 0) {\n <div class=\"flex flex-col gap-2.5\">\n @if (sectionHeader()) {\n <div class=\"text-base font-bold text-sc-dark-grey\">{{ sectionHeader() }}</div>\n }\n <sc-order-items-list\n [items]=\"itemsNotInWarehouseStock()\"\n [selectable]=\"selectable()\"\n [editable]=\"editable()\"\n (selectedItemsIdsChange)=\"selectedItemsHandler($event, itemsNotInWarehouseStock())\"\n />\n </div>\n }\n</div>\n" }]
10885
+ args: [{ standalone: true, selector: 'sc-profstroy-draft-create-dialog', imports: [ReactiveFormsModule, TuiFiles, TuiFileRejectedPipe, TuiLabel, TuiError, TuiFieldErrorPipe, TuiTextfield, TuiNotification, TuiButton, TuiButtonLoading, AsyncPipe], providers: [
10886
+ {
10887
+ provide: TUI_INPUT_FILE_TEXTS,
10888
+ useFactory: () => inject(TUI_INPUT_FILE_TEXTS, { skipSelf: true }).pipe(map((texts) => ({ ...texts, formatRejectionReason: 'Файл должен быть в формате .xlsx' }))),
10889
+ },
10890
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<form\n [formGroup]=\"form\"\n class=\"flex flex-col gap-5\"\n (ngSubmit)=\"submit()\"\n>\n <!-- \u0424\u0430\u0439\u043B. \u041F\u0440\u0438\u0447\u0438\u043D\u0443 \u043E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u0438\u044F \u0444\u0430\u0439\u043B\u0430 \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u0441\u0442\u0440\u043E\u043A\u0430 \u0444\u0430\u0439\u043B\u0430, \u043F\u043E\u0434 \u043F\u043E\u043B\u0435\u043C \u043E\u043D\u0430 \u043D\u0435 \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u0435\u0442\u0441\u044F. \u041D\u0435\u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D\u043D\u043E\u0435 \u043F\u043E\u043B\u0435 \u043F\u043E\u0434\u0441\u0432\u0435\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044F \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0441\u043B\u0435 \u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u0441\u043E\u0437\u0434\u0430\u0442\u044C \u0437\u0430\u044F\u0432\u043A\u0443. -->\n <div class=\"flex flex-col gap-1\">\n @let file = form.controls.file.value;\n @let rejected = file | tuiFileRejected: { accept } | async;\n @if (!file) {\n <label tuiInputFiles>\n <input\n tuiInputFiles\n formControlName=\"file\"\n [accept]=\"accept\"\n [invalid]=\"submitAttempted() ? null : false\"\n />\n </label>\n }\n <tui-files>\n @if (file) {\n <tui-file\n [state]=\"rejected ? 'error' : 'normal'\"\n [file]=\"rejected ?? file\"\n (remove)=\"removeFile()\"\n />\n }\n </tui-files>\n <span class=\"text-body-s text-tui-text-02\">\u0424\u0430\u0439\u043B Excel, \u0432\u044B\u0433\u0440\u0443\u0436\u0435\u043D\u043D\u044B\u0439 \u0438\u0437 \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u044B \u00AB\u041F\u0440\u043E\u0444\u0441\u0442\u0440\u043E\u0439\u00BB: {{ accept }}</span>\n @if (!rejected) {\n @let fileErrorVisible = submitAttempted() || !form.controls.file.hasError('required');\n <tui-error\n formControlName=\"file\"\n [error]=\"fileErrorVisible ? (['required', 'serverResponse'] | tuiFieldError | async) : null\"\n />\n }\n </div>\n\n <!-- \u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0437\u0430\u044F\u0432\u043A\u0438. -->\n <label tuiLabel>\n \u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0437\u0430\u044F\u0432\u043A\u0438\n <tui-textfield>\n <input\n tuiTextfield\n formControlName=\"name\"\n placeholder=\"\u041D\u0430\u043F\u0440\u0438\u043C\u0435\u0440: \u0411\u0430\u043B\u043A\u043E\u043D\u044B, \u043E\u0431\u044A\u0435\u043A\u0442 \u043D\u0430 \u041B\u0435\u043D\u0438\u043D\u0430\"\n autocomplete=\"off\"\n [maxLength]=\"nameMaxLength\"\n />\n </tui-textfield>\n <tui-error\n formControlName=\"name\"\n [error]=\"[] | tuiFieldError | async\"\n />\n </label>\n\n @if (form.errors?.['serverResponse']) {\n <tui-error [error]=\"[] | tuiFieldError | async\" />\n }\n\n <tui-notification\n appearance=\"info\"\n size=\"m\"\n >\n \u041E\u0431\u0440\u0430\u0431\u043E\u0442\u043A\u0430 \u0444\u0430\u0439\u043B\u0430 \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u0435\u0442\u0441\u044F \u0432 \u0444\u043E\u043D\u0435: \u043F\u043E\u0437\u0438\u0446\u0438\u0438 \u0438 \u0446\u0435\u043D\u044B \u043F\u043E\u044F\u0432\u044F\u0442\u0441\u044F \u0432 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u0435 \u0447\u0435\u0440\u0435\u0437 \u043D\u0435\u043A\u043E\u0442\u043E\u0440\u043E\u0435 \u0432\u0440\u0435\u043C\u044F.\n </tui-notification>\n\n <div class=\"flex flex-wrap gap-2\">\n <button\n tuiButton\n type=\"submit\"\n iconStart=\"@tui.sc.send\"\n [loading]=\"submitting()\"\n >\n \u0421\u043E\u0437\u0434\u0430\u0442\u044C \u0437\u0430\u044F\u0432\u043A\u0443\n </button>\n <button\n tuiButton\n type=\"button\"\n appearance=\"secondary\"\n [disabled]=\"submitting()\"\n (click)=\"context.$implicit.complete()\"\n >\n \u041E\u0442\u043C\u0435\u043D\u0430\n </button>\n </div>\n</form>\n" }]
9864
10891
  }] });
9865
10892
 
9866
10893
  /**
9867
10894
  * Компонент страницы черновика заказа (аналог корзины для одного черновика).
9868
10895
  */
9869
10896
  class ScDraftComponent {
10897
+ /**
10898
+ * Инициализирует экземпляр класса {@link ScDraftComponent}.
10899
+ */
9870
10900
  constructor() {
9871
10901
  /**
9872
10902
  * Сервис маршрутизации.
@@ -9880,6 +10910,10 @@ class ScDraftComponent {
9880
10910
  * Сервис для работы с черновиками заказов.
9881
10911
  */
9882
10912
  this.draftsService = inject(ScOrderDraftsService);
10913
+ /**
10914
+ * Сервис уведомлений.
10915
+ */
10916
+ this.alerts = inject(TuiAlertService);
9883
10917
  /**
9884
10918
  * Сигнал с данными текущего черновика заказа (из параметра orderId в URL).
9885
10919
  */
@@ -9900,6 +10934,45 @@ class ScDraftComponent {
9900
10934
  * Текущий ключ маршрута.
9901
10935
  */
9902
10936
  this.isDraftPage = toSignal(this.route.data.pipe(map((data) => data['routes_key'] === ScRouteKeys.draft), distinctUntilChanged()), { initialValue: false });
10937
+ /**
10938
+ * Признак черновика Профстрой.
10939
+ */
10940
+ this.isProfstroy = computed(() => this.draft()?.isProfstroy === true);
10941
+ /**
10942
+ * Признак, что идёт обработка черновика Профстрой.
10943
+ */
10944
+ this.isProcessing = computed(() => this.draft()?.actualization?.isProcessing === true);
10945
+ /**
10946
+ * Сервис текущего момента для проверки срока актуализации.
10947
+ */
10948
+ this.expirationClock = inject(ScDraftExpirationClockService);
10949
+ /**
10950
+ * Текущий момент для проверки срока актуализации из общего источника. Обновляется, пока открыт черновик Профстрой.
10951
+ */
10952
+ this.now = this.expirationClock.nowWhile(this.isProfstroy);
10953
+ /**
10954
+ * Признак выполнения актуализации или пересборки черновика Профстрой.
10955
+ */
10956
+ this.actionRunning = signal(false);
10957
+ /**
10958
+ * Признак, что для черновика выполняются или ждут очереди запросы изменения или перечитывания.
10959
+ */
10960
+ this.queueBusy = toSignal(toObservable(this.draft).pipe(map((draft) => draft?.id), distinctUntilChanged(), switchMap((draftId) => (draftId === undefined ? of(false) : this.draftsService.isBusy$(draftId)))), { initialValue: false });
10961
+ /**
10962
+ * Признак, что действия над черновиком Профстрой недоступны, пока выполняется действие или запрос черновика.
10963
+ */
10964
+ this.busy = computed(() => this.actionRunning() || this.queueBusy());
10965
+ /**
10966
+ * Признак, что можно менять состав черновика Профстрой.
10967
+ */
10968
+ this.canEditProfstroy = computed(() => this.isDraftPage() && !this.actionRunning() && !this.isProcessing());
10969
+ /**
10970
+ * Причина, по которой нельзя оформить заказ из черновика Профстрой, или пустая строка.
10971
+ */
10972
+ this.profstroyOrderBlockReason = computed(() => {
10973
+ const reason = this.draft()?.getOrderBlockReason(this.now()) ?? '';
10974
+ return reason || (this.selectedItemsIds().length === 0 ? 'Выберите позиции для заказа.' : '');
10975
+ });
9903
10976
  /**
9904
10977
  * Ссылка для автоматического управления уничтожением зависимостей.
9905
10978
  */
@@ -9908,6 +10981,7 @@ class ScDraftComponent {
9908
10981
  * Сервис установки SEO-данных приложения.
9909
10982
  */
9910
10983
  this.seoService = inject(ScSeoService);
10984
+ this.watchProfstroyProcessing();
9911
10985
  }
9912
10986
  /** @inheritDoc */
9913
10987
  ngOnInit() {
@@ -9928,7 +11002,32 @@ class ScDraftComponent {
9928
11002
  this.router.navigate(['orders', 'new', this.draft()?.id, 'draft'], { queryParams: { itemsIds: this.selectedItemsIds() } }).catch(noop);
9929
11003
  }
9930
11004
  /**
9931
- * Удаляет черновик и перенаправляет к списку черновиков.
11005
+ * Запускает актуализацию черновика Профстрой.
11006
+ */
11007
+ actualizeProfstroy() {
11008
+ this.runProfstroyAction((draftId) => this.draftsService.actualizeProfstroy$(draftId));
11009
+ }
11010
+ /**
11011
+ * Запускает пересборку черновика Профстрой из файла после подтверждения.
11012
+ */
11013
+ reloadProfstroy() {
11014
+ this.dialogService
11015
+ .open(TUI_CONFIRM, {
11016
+ label: 'Собрать заявку заново?',
11017
+ data: {
11018
+ content: 'Заявка будет собрана заново из загруженного файла. Изменённые количество, метраж и маркировка позиций, а также удалённые позиции будут потеряны. Продолжить?',
11019
+ yes: 'Да, собрать заново',
11020
+ no: 'Отмена',
11021
+ },
11022
+ })
11023
+ .pipe(filter(Boolean), takeUntilDestroyed(this.destroyRef))
11024
+ .subscribe(() => {
11025
+ this.runProfstroyAction((draftId) => this.draftsService.reloadProfstroy$(draftId));
11026
+ });
11027
+ }
11028
+ /**
11029
+ * Удаляет черновик и перенаправляет к списку черновиков. Пока черновик удаляется, действия над ним заблокированы.
11030
+ * Если удалить не удалось, пользователь остаётся на странице, сообщение об ошибке показывает перехватчик приложения.
9932
11031
  */
9933
11032
  clearDraft() {
9934
11033
  const { id } = this.draft() ?? {};
@@ -9944,29 +11043,86 @@ class ScDraftComponent {
9944
11043
  no: 'Нет, оставить',
9945
11044
  },
9946
11045
  })
9947
- .pipe(filter(Boolean), switchMap(() => this.draftsService.delete$(id)), takeUntilDestroyed(this.destroyRef))
9948
- .subscribe(() => {
9949
- this.deleteEvent.emit();
9950
- this.router.navigate(['/drafts']).catch(noop);
11046
+ .pipe(filter(Boolean), switchMap(() => {
11047
+ this.actionRunning.set(true);
11048
+ return this.draftsService.delete$(id).pipe(finalize(() => {
11049
+ this.actionRunning.set(false);
11050
+ }));
11051
+ }), takeUntilDestroyed(this.destroyRef))
11052
+ .subscribe({
11053
+ next: () => {
11054
+ this.deleteEvent.emit();
11055
+ this.router.navigate(['/drafts']).catch(noop);
11056
+ },
11057
+ error: noop,
11058
+ });
11059
+ }
11060
+ /**
11061
+ * Выполняет действие над черновиком Профстрой. Пока действие выполняется, остальные заблокированы.
11062
+ * Ответ попадает в кэш черновика, после отказа 403 или 409 сервис сам перезапрашивает черновик. Сообщение об ошибке показывает перехватчик приложения.
11063
+ *
11064
+ * @param action Функция, возвращающая запрос действия по идентификатору черновика.
11065
+ */
11066
+ runProfstroyAction(action) {
11067
+ const draftId = this.draft()?.id;
11068
+ if (draftId === undefined || this.busy()) {
11069
+ return;
11070
+ }
11071
+ this.actionRunning.set(true);
11072
+ action(draftId)
11073
+ .pipe(finalize(() => {
11074
+ this.actionRunning.set(false);
11075
+ }), takeUntilDestroyed(this.destroyRef))
11076
+ .subscribe({ error: noop });
11077
+ }
11078
+ /**
11079
+ * Пока черновик Профстрой на его странице в обработке, опрашивает состояние лёгким запросом списка и после окончания обработки перезапрашивает черновик.
11080
+ * Опрос идёт, пока перезапрошенный черновик остаётся в обработке: неудачный перезапрос повторяется на следующем шаге.
11081
+ * Если черновика больше нет (удалён в другой вкладке), опрос завершается, а пользователь возвращается к списку. В списке черновиков состояние опрашивает сам список.
11082
+ */
11083
+ watchProfstroyProcessing() {
11084
+ toObservable(computed(() => (this.isDraftPage() && this.isProcessing() ? this.draft()?.id : undefined)))
11085
+ .pipe(distinctUntilChanged(), switchMap((draftId) => draftId === undefined
11086
+ ? EMPTY
11087
+ : this.draftsService.watchShortList$([draftId]).pipe(filter((drafts) => !drafts.some((short) => short.id === draftId && short.actualization?.isProcessing)), exhaustMap(() => this.draftsService.fetch$(draftId).pipe(catchError((error) => (ScApiErrorHelper.isNotFound(error) ? of(null) : EMPTY)))), takeWhile((draft) => draft?.actualization?.isProcessing === true, true), filter((draft) => draft === null), map(() => draftId))), takeUntilDestroyed(this.destroyRef))
11088
+ .subscribe((draftId) => {
11089
+ this.leaveMissingDraft(draftId);
9951
11090
  });
9952
11091
  }
11092
+ /**
11093
+ * Сообщает, что черновик не найден, и возвращает к списку черновиков.
11094
+ *
11095
+ * @param draftId Идентификатор черновика.
11096
+ */
11097
+ leaveMissingDraft(draftId) {
11098
+ this.alerts.open(`Черновик №${draftId} не найден. Возможно, он удалён.`, { appearance: 'warning' }).subscribe();
11099
+ this.router.navigate(['/drafts']).catch(noop);
11100
+ }
9953
11101
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScDraftComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9954
11102
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScDraftComponent, isStandalone: true, selector: "sc-draft", inputs: { draft: { classPropertyName: "draft", publicName: "draft", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { draft: "draftChange", deleteEvent: "deleteEvent" }, providers: [
9955
11103
  {
9956
11104
  provide: SC_ORDER_LOADER,
9957
11105
  useExisting: ScOrderDraftsService,
9958
11106
  },
9959
- ], ngImport: i0, template: "@let items = draft()?.products;\n\n@if (items) {\n <div class=\"flex flex-col gap-12\">\n <div class=\"flex flex-col gap-5\">\n @if (items.length) {\n <sc-order-items-list-with-actions\n scOrder\n [orderId]=\"draft()?.id\"\n [items]=\"items\"\n [editable]=\"isDraftPage()\"\n (selectedItemsIdsChange)=\"selectedItemsIds.set($event)\"\n >\n <div\n footerActions\n class=\"flex w-full flex-col items-center gap-x-2.5 gap-y-4 sm:flex-row sm:flex-wrap sm:items-start\"\n >\n <button\n tuiButton\n appearance=\"secondary\"\n (click)=\"clearDraft()\"\n iconStart=\"@tui.trash-2\"\n >\n \u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\n </button>\n\n <button\n tuiButton\n type=\"button\"\n class=\"sm:ml-auto\"\n iconStart=\"@tui.shopping-cart\"\n [disabled]=\"selectedItemsIds().length === 0\"\n (click)=\"goToNewOrder()\"\n >\n \u0417\u0430\u043A\u0430\u0437\u0430\u0442\u044C\n </button>\n </div>\n </sc-order-items-list-with-actions>\n } @else {\n <p class=\"text-base font-bold\">\u0427\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u043F\u0443\u0441\u0442.</p>\n <div\n footerActions\n class=\"flex gap-2\"\n >\n <button\n tuiButton\n appearance=\"secondary\"\n (click)=\"clearDraft()\"\n iconStart=\"@tui.trash-2\"\n >\n \u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\n </button>\n </div>\n }\n </div>\n </div>\n} @else {\n <div class=\"flex flex-col gap-12\">\n <div class=\"flex flex-col gap-5\">\n @for (item of [1, 2, 3, 4]; track item) {\n <div class=\"tui-skeleton flex h-[8.25rem] w-full overflow-hidden rounded-xl\"></div>\n }\n <div class=\"flex gap-2\">\n <div class=\"tui-skeleton h-10 w-40 rounded-md\"></div>\n <div class=\"tui-skeleton h-10 w-36 rounded-md\"></div>\n </div>\n </div>\n </div>\n}\n", dependencies: [{ kind: "component", type: ScOrderItemsListWithActionsComponent, selector: "sc-order-items-list-with-actions", inputs: ["items", "selectable", "editable", "selectedItemsIds"], outputs: ["selectedItemsIdsChange"] }, { kind: "directive", type: ScOrderAccessorDirective, selector: "[scOrder]", inputs: ["orderId"] }, { kind: "directive", type: TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
11107
+ ], ngImport: i0, template: "@let currentDraft = draft();\n\n@if (currentDraft && currentDraft.isProfstroy) {\n <div class=\"flex flex-col gap-5\">\n <sc-profstroy-draft-badges\n [actualization]=\"currentDraft.actualization\"\n [now]=\"now()\"\n />\n\n <sc-profstroy-actualization-panel\n [draft]=\"currentDraft\"\n [now]=\"now()\"\n [busy]=\"busy() || !isDraftPage()\"\n (actualize)=\"actualizeProfstroy()\"\n (reload)=\"reloadProfstroy()\"\n />\n\n @if (currentDraft.products.length) {\n <sc-order-items-list\n scOrder\n [orderId]=\"currentDraft.id\"\n [items]=\"currentDraft.products\"\n [grouped]=\"true\"\n [markUnavailable]=\"true\"\n [useLinePrice]=\"true\"\n [measuredInDialog]=\"true\"\n [editable]=\"canEditProfstroy()\"\n [selectable]=\"true\"\n deleteConfirmationTarget=\"\u0438\u0437 \u0437\u0430\u044F\u0432\u043A\u0438\"\n totalLabel=\"\u0418\u0422\u041E\u0413\u041E \u041A \u0417\u0410\u041A\u0410\u0417\u0423\"\n (selectedItemsIdsChange)=\"selectedItemsIds.set($event)\"\n />\n } @else {\n <p class=\"text-base font-bold\">{{ isProcessing() ? '\u041F\u043E\u0437\u0438\u0446\u0438\u0438 \u043F\u043E\u044F\u0432\u044F\u0442\u0441\u044F \u043F\u043E\u0441\u043B\u0435 \u043E\u0431\u0440\u0430\u0431\u043E\u0442\u043A\u0438 \u0444\u0430\u0439\u043B\u0430' : '\u0412 \u0437\u0430\u044F\u0432\u043A\u0435 \u043D\u0435\u0442 \u043F\u043E\u0437\u0438\u0446\u0438\u0439' }}</p>\n }\n\n <div class=\"flex flex-col items-center gap-x-2.5 gap-y-4 sm:flex-row sm:flex-wrap-reverse sm:items-start\">\n <button\n tuiButton\n appearance=\"secondary\"\n iconStart=\"@tui.trash-2\"\n [disabled]=\"busy()\"\n (click)=\"clearDraft()\"\n >\n \u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\n </button>\n\n <div class=\"flex flex-col items-center gap-1 sm:ml-auto sm:items-end\">\n <button\n tuiButton\n type=\"button\"\n iconStart=\"@tui.shopping-cart\"\n [disabled]=\"busy() || !!profstroyOrderBlockReason()\"\n (click)=\"goToNewOrder()\"\n >\n \u041E\u0444\u043E\u0440\u043C\u0438\u0442\u044C \u0437\u0430\u043A\u0430\u0437\n </button>\n @if (profstroyOrderBlockReason(); as reason) {\n <span class=\"text-body-s text-tui-text-02\">{{ reason }}</span>\n }\n </div>\n </div>\n </div>\n} @else {\n @let items = draft()?.products;\n\n @if (items) {\n <div class=\"flex flex-col gap-12\">\n <div class=\"flex flex-col gap-5\">\n @if (items.length) {\n <sc-order-items-list-with-actions\n scOrder\n [orderId]=\"draft()?.id\"\n [items]=\"items\"\n [editable]=\"isDraftPage()\"\n deleteConfirmationTarget=\"\u0438\u0437 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u0430\"\n (selectedItemsIdsChange)=\"selectedItemsIds.set($event)\"\n >\n <div\n footerActions\n class=\"flex w-full flex-col items-center gap-x-2.5 gap-y-4 sm:flex-row sm:flex-wrap sm:items-start\"\n >\n <button\n tuiButton\n appearance=\"secondary\"\n (click)=\"clearDraft()\"\n iconStart=\"@tui.trash-2\"\n >\n \u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\n </button>\n\n <button\n tuiButton\n type=\"button\"\n class=\"sm:ml-auto\"\n iconStart=\"@tui.shopping-cart\"\n [disabled]=\"selectedItemsIds().length === 0\"\n (click)=\"goToNewOrder()\"\n >\n \u0417\u0430\u043A\u0430\u0437\u0430\u0442\u044C\n </button>\n </div>\n </sc-order-items-list-with-actions>\n } @else {\n <p class=\"text-base font-bold\">\u0427\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u043F\u0443\u0441\u0442.</p>\n <div\n footerActions\n class=\"flex gap-2\"\n >\n <button\n tuiButton\n appearance=\"secondary\"\n (click)=\"clearDraft()\"\n iconStart=\"@tui.trash-2\"\n >\n \u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\n </button>\n </div>\n }\n </div>\n </div>\n } @else {\n <div class=\"flex flex-col gap-12\">\n <div class=\"flex flex-col gap-5\">\n @for (item of [1, 2, 3, 4]; track item) {\n <div class=\"tui-skeleton flex h-[8.25rem] w-full overflow-hidden rounded-xl\"></div>\n }\n <div class=\"flex gap-2\">\n <div class=\"tui-skeleton h-10 w-40 rounded-md\"></div>\n <div class=\"tui-skeleton h-10 w-36 rounded-md\"></div>\n </div>\n </div>\n </div>\n }\n}\n", dependencies: [{ kind: "component", type: ScOrderItemsListWithActionsComponent, selector: "sc-order-items-list-with-actions", inputs: ["items", "selectable", "editable", "useLinePrice", "deleteConfirmationTarget", "totalLabel", "selectedItemsIds"], outputs: ["selectedItemsIdsChange"] }, { kind: "directive", type: ScOrderAccessorDirective, selector: "[scOrder]", inputs: ["orderId"] }, { kind: "component", type: ScProfstroyActualizationPanelComponent, selector: "sc-profstroy-actualization-panel", inputs: ["draft", "now", "busy"], outputs: ["actualize", "reload"] }, { kind: "component", type: ScProfstroyDraftBadgesComponent, selector: "sc-profstroy-draft-badges", inputs: ["actualization", "now"] }, { kind: "component", type: ScOrderItemsListComponent, selector: "sc-order-items-list", inputs: ["selectable", "editable", "grouped", "markUnavailable", "useLinePrice", "measuredInDialog", "deleteConfirmationTarget", "totalLabel", "items"], outputs: ["selectedItemsIdsChange"] }, { kind: "directive", type: TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9960
11108
  }
9961
11109
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScDraftComponent, decorators: [{
9962
11110
  type: Component,
9963
- args: [{ standalone: true, selector: 'sc-draft', imports: [ScOrderItemsListWithActionsComponent, ScOrderAccessorDirective, TuiButton, TuiIcons], providers: [
11111
+ args: [{ standalone: true, selector: 'sc-draft', imports: [
11112
+ ScOrderItemsListWithActionsComponent,
11113
+ ScOrderAccessorDirective,
11114
+ ScProfstroyActualizationPanelComponent,
11115
+ ScProfstroyDraftBadgesComponent,
11116
+ ScOrderItemsListComponent,
11117
+ TuiButton,
11118
+ TuiIcons,
11119
+ ], providers: [
9964
11120
  {
9965
11121
  provide: SC_ORDER_LOADER,
9966
11122
  useExisting: ScOrderDraftsService,
9967
11123
  },
9968
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let items = draft()?.products;\n\n@if (items) {\n <div class=\"flex flex-col gap-12\">\n <div class=\"flex flex-col gap-5\">\n @if (items.length) {\n <sc-order-items-list-with-actions\n scOrder\n [orderId]=\"draft()?.id\"\n [items]=\"items\"\n [editable]=\"isDraftPage()\"\n (selectedItemsIdsChange)=\"selectedItemsIds.set($event)\"\n >\n <div\n footerActions\n class=\"flex w-full flex-col items-center gap-x-2.5 gap-y-4 sm:flex-row sm:flex-wrap sm:items-start\"\n >\n <button\n tuiButton\n appearance=\"secondary\"\n (click)=\"clearDraft()\"\n iconStart=\"@tui.trash-2\"\n >\n \u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\n </button>\n\n <button\n tuiButton\n type=\"button\"\n class=\"sm:ml-auto\"\n iconStart=\"@tui.shopping-cart\"\n [disabled]=\"selectedItemsIds().length === 0\"\n (click)=\"goToNewOrder()\"\n >\n \u0417\u0430\u043A\u0430\u0437\u0430\u0442\u044C\n </button>\n </div>\n </sc-order-items-list-with-actions>\n } @else {\n <p class=\"text-base font-bold\">\u0427\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u043F\u0443\u0441\u0442.</p>\n <div\n footerActions\n class=\"flex gap-2\"\n >\n <button\n tuiButton\n appearance=\"secondary\"\n (click)=\"clearDraft()\"\n iconStart=\"@tui.trash-2\"\n >\n \u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\n </button>\n </div>\n }\n </div>\n </div>\n} @else {\n <div class=\"flex flex-col gap-12\">\n <div class=\"flex flex-col gap-5\">\n @for (item of [1, 2, 3, 4]; track item) {\n <div class=\"tui-skeleton flex h-[8.25rem] w-full overflow-hidden rounded-xl\"></div>\n }\n <div class=\"flex gap-2\">\n <div class=\"tui-skeleton h-10 w-40 rounded-md\"></div>\n <div class=\"tui-skeleton h-10 w-36 rounded-md\"></div>\n </div>\n </div>\n </div>\n}\n" }]
9969
- }] });
11124
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let currentDraft = draft();\n\n@if (currentDraft && currentDraft.isProfstroy) {\n <div class=\"flex flex-col gap-5\">\n <sc-profstroy-draft-badges\n [actualization]=\"currentDraft.actualization\"\n [now]=\"now()\"\n />\n\n <sc-profstroy-actualization-panel\n [draft]=\"currentDraft\"\n [now]=\"now()\"\n [busy]=\"busy() || !isDraftPage()\"\n (actualize)=\"actualizeProfstroy()\"\n (reload)=\"reloadProfstroy()\"\n />\n\n @if (currentDraft.products.length) {\n <sc-order-items-list\n scOrder\n [orderId]=\"currentDraft.id\"\n [items]=\"currentDraft.products\"\n [grouped]=\"true\"\n [markUnavailable]=\"true\"\n [useLinePrice]=\"true\"\n [measuredInDialog]=\"true\"\n [editable]=\"canEditProfstroy()\"\n [selectable]=\"true\"\n deleteConfirmationTarget=\"\u0438\u0437 \u0437\u0430\u044F\u0432\u043A\u0438\"\n totalLabel=\"\u0418\u0422\u041E\u0413\u041E \u041A \u0417\u0410\u041A\u0410\u0417\u0423\"\n (selectedItemsIdsChange)=\"selectedItemsIds.set($event)\"\n />\n } @else {\n <p class=\"text-base font-bold\">{{ isProcessing() ? '\u041F\u043E\u0437\u0438\u0446\u0438\u0438 \u043F\u043E\u044F\u0432\u044F\u0442\u0441\u044F \u043F\u043E\u0441\u043B\u0435 \u043E\u0431\u0440\u0430\u0431\u043E\u0442\u043A\u0438 \u0444\u0430\u0439\u043B\u0430' : '\u0412 \u0437\u0430\u044F\u0432\u043A\u0435 \u043D\u0435\u0442 \u043F\u043E\u0437\u0438\u0446\u0438\u0439' }}</p>\n }\n\n <div class=\"flex flex-col items-center gap-x-2.5 gap-y-4 sm:flex-row sm:flex-wrap-reverse sm:items-start\">\n <button\n tuiButton\n appearance=\"secondary\"\n iconStart=\"@tui.trash-2\"\n [disabled]=\"busy()\"\n (click)=\"clearDraft()\"\n >\n \u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\n </button>\n\n <div class=\"flex flex-col items-center gap-1 sm:ml-auto sm:items-end\">\n <button\n tuiButton\n type=\"button\"\n iconStart=\"@tui.shopping-cart\"\n [disabled]=\"busy() || !!profstroyOrderBlockReason()\"\n (click)=\"goToNewOrder()\"\n >\n \u041E\u0444\u043E\u0440\u043C\u0438\u0442\u044C \u0437\u0430\u043A\u0430\u0437\n </button>\n @if (profstroyOrderBlockReason(); as reason) {\n <span class=\"text-body-s text-tui-text-02\">{{ reason }}</span>\n }\n </div>\n </div>\n </div>\n} @else {\n @let items = draft()?.products;\n\n @if (items) {\n <div class=\"flex flex-col gap-12\">\n <div class=\"flex flex-col gap-5\">\n @if (items.length) {\n <sc-order-items-list-with-actions\n scOrder\n [orderId]=\"draft()?.id\"\n [items]=\"items\"\n [editable]=\"isDraftPage()\"\n deleteConfirmationTarget=\"\u0438\u0437 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u0430\"\n (selectedItemsIdsChange)=\"selectedItemsIds.set($event)\"\n >\n <div\n footerActions\n class=\"flex w-full flex-col items-center gap-x-2.5 gap-y-4 sm:flex-row sm:flex-wrap sm:items-start\"\n >\n <button\n tuiButton\n appearance=\"secondary\"\n (click)=\"clearDraft()\"\n iconStart=\"@tui.trash-2\"\n >\n \u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\n </button>\n\n <button\n tuiButton\n type=\"button\"\n class=\"sm:ml-auto\"\n iconStart=\"@tui.shopping-cart\"\n [disabled]=\"selectedItemsIds().length === 0\"\n (click)=\"goToNewOrder()\"\n >\n \u0417\u0430\u043A\u0430\u0437\u0430\u0442\u044C\n </button>\n </div>\n </sc-order-items-list-with-actions>\n } @else {\n <p class=\"text-base font-bold\">\u0427\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u043F\u0443\u0441\u0442.</p>\n <div\n footerActions\n class=\"flex gap-2\"\n >\n <button\n tuiButton\n appearance=\"secondary\"\n (click)=\"clearDraft()\"\n iconStart=\"@tui.trash-2\"\n >\n \u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\n </button>\n </div>\n }\n </div>\n </div>\n } @else {\n <div class=\"flex flex-col gap-12\">\n <div class=\"flex flex-col gap-5\">\n @for (item of [1, 2, 3, 4]; track item) {\n <div class=\"tui-skeleton flex h-[8.25rem] w-full overflow-hidden rounded-xl\"></div>\n }\n <div class=\"flex gap-2\">\n <div class=\"tui-skeleton h-10 w-40 rounded-md\"></div>\n <div class=\"tui-skeleton h-10 w-36 rounded-md\"></div>\n </div>\n </div>\n </div>\n }\n}\n" }]
11125
+ }], ctorParameters: () => [] });
9970
11126
 
9971
11127
  /**
9972
11128
  * Компонент статуса оплаты заказа.
@@ -11121,14 +12277,18 @@ class ScPreviewSampleModule {
11121
12277
  TuiHovered,
11122
12278
  TuiButton,
11123
12279
  TuiButtonLoading,
11124
- TuiLabel], exports: [ScPreviewSampleComponent] }); }
12280
+ TuiLabel,
12281
+ RecaptchaFormsModule,
12282
+ RecaptchaModule], exports: [ScPreviewSampleComponent] }); }
11125
12283
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScPreviewSampleModule, imports: [CommonModule,
11126
12284
  ReactiveFormsModule,
11127
12285
  TuiError,
11128
12286
  ScFormFieldsModule,
11129
12287
  TuiInputModule,
11130
12288
  TuiInputPhoneModule, i2$1.TuiCarouselComponent, TuiAvatar,
11131
- TuiButtonLoading] }); }
12289
+ TuiButtonLoading,
12290
+ RecaptchaFormsModule,
12291
+ RecaptchaModule] }); }
11132
12292
  }
11133
12293
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScPreviewSampleModule, decorators: [{
11134
12294
  type: NgModule,
@@ -11148,6 +12308,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
11148
12308
  TuiButton,
11149
12309
  TuiButtonLoading,
11150
12310
  TuiLabel,
12311
+ RecaptchaFormsModule,
12312
+ RecaptchaModule,
11151
12313
  ],
11152
12314
  exports: [ScPreviewSampleComponent],
11153
12315
  }]
@@ -11167,255 +12329,6 @@ const scGetCurrentRoute = (route) => {
11167
12329
  return currentRoute;
11168
12330
  };
11169
12331
 
11170
- /**
11171
- * Токен времени ожидания загрузки скрипта Yandex SmartCaptcha в миллисекундах.
11172
- */
11173
- const SC_SMART_CAPTCHA_LOAD_TIMEOUT = new InjectionToken('SC_SMART_CAPTCHA_LOAD_TIMEOUT', {
11174
- factory: () => 20_000,
11175
- });
11176
-
11177
- /**
11178
- * Сервис загрузки скрипта Yandex SmartCaptcha.
11179
- */
11180
- class ScSmartCaptchaLoaderService {
11181
- constructor() {
11182
- /**
11183
- * Сервис для вставки внешних скриптов.
11184
- */
11185
- this.scriptsService = inject(ScScriptsService);
11186
- /**
11187
- * Окно браузера с API Yandex SmartCaptcha.
11188
- */
11189
- this.window = inject(WA_WINDOW);
11190
- /**
11191
- * Признак того, что приложение запущено в браузере.
11192
- */
11193
- this.isBrowser = inject(IS_BROWSER);
11194
- /**
11195
- * Время ожидания загрузки скрипта в миллисекундах.
11196
- */
11197
- this.loadTimeout = inject(SC_SMART_CAPTCHA_LOAD_TIMEOUT);
11198
- /**
11199
- * Имя функции окна, которую скрипт вызывает после загрузки.
11200
- */
11201
- this.onloadCallbackName = 'scSmartCaptchaOnload';
11202
- /**
11203
- * Адрес скрипта.
11204
- */
11205
- this.scriptUrl = `https://smartcaptcha.cloud.yandex.ru/captcha.js?render=onload&onload=${this.onloadCallbackName}`;
11206
- /**
11207
- * Загрузка API, общая для всех виджетов. Скрипт загружается один раз; отписка и истечение ожидания у подписчика загрузку не прерывают, после ошибки следующая подписка загружает скрипт заново.
11208
- */
11209
- this.loading$ = defer(() => this.load$()).pipe(shareReplay(1));
11210
- /**
11211
- * {@link Observable} API Yandex SmartCaptcha. Значения и ошибки приходят вне зоны Angular, ожидание первого значения ограничено {@link SC_SMART_CAPTCHA_LOAD_TIMEOUT}.
11212
- */
11213
- this.api$ = this.loading$.pipe(timeout({ first: this.loadTimeout }), tuiZonefree());
11214
- }
11215
- /**
11216
- * Возвращает API Yandex SmartCaptcha, при необходимости загружая скрипт. На сервере ничего не возвращает.
11217
- */
11218
- load$() {
11219
- if (!this.isBrowser) {
11220
- return EMPTY;
11221
- }
11222
- if (this.window.smartCaptcha) {
11223
- return of(this.window.smartCaptcha);
11224
- }
11225
- return merge(this.onload$(), this.scriptsService.loadScript$(this.scriptUrl).pipe(ignoreElements()));
11226
- }
11227
- /**
11228
- * Ждёт вызова функции из параметра `onload` адреса скрипта и возвращает API, которое скрипт к этому моменту создал.
11229
- */
11230
- onload$() {
11231
- return new Observable((subscriber) => {
11232
- this.window[this.onloadCallbackName] = () => {
11233
- if (this.window.smartCaptcha) {
11234
- subscriber.next(this.window.smartCaptcha);
11235
- subscriber.complete();
11236
- }
11237
- else {
11238
- subscriber.error(new Error('Yandex SmartCaptcha API is unavailable'));
11239
- }
11240
- };
11241
- return () => {
11242
- this.window[this.onloadCallbackName] = undefined;
11243
- };
11244
- });
11245
- }
11246
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScSmartCaptchaLoaderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
11247
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScSmartCaptchaLoaderService, providedIn: 'root' }); }
11248
- }
11249
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScSmartCaptchaLoaderService, decorators: [{
11250
- type: Injectable,
11251
- args: [{
11252
- providedIn: 'root',
11253
- }]
11254
- }] });
11255
-
11256
- /**
11257
- * Токен настроек Yandex SmartCaptcha. Задаётся приложением.
11258
- */
11259
- const SC_SMART_CAPTCHA_SETTINGS = new InjectionToken('SC_SMART_CAPTCHA_SETTINGS');
11260
-
11261
- /* eslint-disable no-use-before-define */
11262
- /**
11263
- * Виджет Yandex SmartCaptcha. Значение поля ввода - токен прохождения проверки, `null` пока проверка не пройдена или токен истёк.
11264
- */
11265
- class ScSmartCaptchaComponent {
11266
- /**
11267
- * Инициализирует экземпляр класса {@link ScSmartCaptchaComponent}.
11268
- */
11269
- constructor() {
11270
- /**
11271
- * Контейнер, в котором отрисовывается виджет.
11272
- */
11273
- this.container = viewChild.required('container');
11274
- /**
11275
- * Настройки Yandex SmartCaptcha.
11276
- */
11277
- this.settings = inject(SC_SMART_CAPTCHA_SETTINGS);
11278
- /**
11279
- * Сервис загрузки скрипта Yandex SmartCaptcha.
11280
- */
11281
- this.loader = inject(ScSmartCaptchaLoaderService);
11282
- /**
11283
- * {@link NgZone} для отрисовки виджета вне зоны Angular и возврата его событий в зону.
11284
- */
11285
- this.ngZone = inject(NgZone);
11286
- /**
11287
- * Ссылка для автоматического управления уничтожением зависимостей.
11288
- */
11289
- this.destroyRef = inject(DestroyRef);
11290
- /**
11291
- * Признак того, что капчу не удалось загрузить.
11292
- */
11293
- this.hasError = signal(false);
11294
- /**
11295
- * Функции отписки от событий виджета.
11296
- */
11297
- this.unsubscribers = [];
11298
- /**
11299
- * Обработчик изменения значения поля ввода.
11300
- */
11301
- this.onChange = noop;
11302
- /**
11303
- * Обработчик касания поля ввода.
11304
- */
11305
- this.onTouched = noop;
11306
- afterNextRender(() => {
11307
- this.load();
11308
- });
11309
- this.destroyRef.onDestroy(() => {
11310
- this.destroyWidget();
11311
- });
11312
- }
11313
- /** @inheritDoc */
11314
- writeValue(token) {
11315
- const { api, widgetId } = this;
11316
- if (!token && api && widgetId !== undefined) {
11317
- this.ngZone.runOutsideAngular(() => {
11318
- api.reset(widgetId);
11319
- });
11320
- }
11321
- }
11322
- /** @inheritDoc */
11323
- registerOnChange(onChange) {
11324
- this.onChange = onChange;
11325
- }
11326
- /** @inheritDoc */
11327
- registerOnTouched(onTouched) {
11328
- this.onTouched = onTouched;
11329
- }
11330
- /**
11331
- * Повторяет загрузку капчи после ошибки.
11332
- */
11333
- retry() {
11334
- this.hasError.set(false);
11335
- this.load();
11336
- }
11337
- /**
11338
- * Загружает API Yandex SmartCaptcha и отрисовывает виджет (API приходит вне зоны Angular). При ошибке загрузки или отрисовки показывает сообщение.
11339
- */
11340
- load() {
11341
- this.loader.api$
11342
- .pipe(tap((api) => {
11343
- this.render(api);
11344
- }), takeUntilDestroyed(this.destroyRef))
11345
- .subscribe({
11346
- error: () => {
11347
- this.showError();
11348
- },
11349
- });
11350
- }
11351
- /**
11352
- * Отрисовывает виджет и подписывается на его события.
11353
- *
11354
- * @param api API Yandex SmartCaptcha.
11355
- */
11356
- render(api) {
11357
- const widgetId = api.render(this.container().nativeElement, { sitekey: this.settings.sitekey, hl: this.settings.language });
11358
- this.api = api;
11359
- this.widgetId = widgetId;
11360
- this.unsubscribers = [
11361
- api.subscribe(widgetId, 'success', (token) => {
11362
- this.updateValue(typeof token === 'string' ? token : null);
11363
- }),
11364
- api.subscribe(widgetId, 'token-expired', () => {
11365
- this.updateValue(null);
11366
- api.reset(widgetId);
11367
- }),
11368
- api.subscribe(widgetId, 'javascript-error', () => {
11369
- this.showError();
11370
- }),
11371
- ];
11372
- }
11373
- /**
11374
- * Передаёт форме новое значение токена.
11375
- *
11376
- * @param token Токен прохождения проверки.
11377
- */
11378
- updateValue(token) {
11379
- this.ngZone.run(() => {
11380
- this.onChange(token);
11381
- this.onTouched();
11382
- });
11383
- }
11384
- /**
11385
- * Удаляет виджет, очищает токен и показывает сообщение об ошибке.
11386
- */
11387
- showError() {
11388
- this.destroyWidget();
11389
- this.ngZone.run(() => {
11390
- this.hasError.set(true);
11391
- this.onChange(null);
11392
- });
11393
- }
11394
- /**
11395
- * Удаляет виджет вместе с созданными им элементами и обработчиками.
11396
- */
11397
- destroyWidget() {
11398
- this.unsubscribers.forEach((unsubscribe) => {
11399
- unsubscribe();
11400
- });
11401
- this.unsubscribers = [];
11402
- const { api, widgetId } = this;
11403
- if (api && widgetId !== undefined) {
11404
- this.ngZone.runOutsideAngular(() => {
11405
- api.destroy(widgetId);
11406
- });
11407
- }
11408
- this.api = undefined;
11409
- this.widgetId = undefined;
11410
- }
11411
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScSmartCaptchaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
11412
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScSmartCaptchaComponent, isStandalone: true, selector: "sc-smart-captcha", providers: [tuiProvide(NG_VALUE_ACCESSOR, ScSmartCaptchaComponent, true)], viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n #container\n class=\"min-h-[7.375rem]\"\n [class.hidden]=\"hasError()\"\n></div>\n@if (hasError()) {\n <p\n role=\"alert\"\n class=\"text-body-s text-tui-text-negative\"\n >\n \u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443 \u00AB\u042F \u043D\u0435 \u0440\u043E\u0431\u043E\u0442\u00BB.\n <button\n tuiLink\n type=\"button\"\n (click)=\"retry()\"\n >\n \u041F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u044C\n </button>\n </p>\n}\n", dependencies: [{ kind: "directive", type: TuiLink, selector: "a[tuiLink], button[tuiLink]", inputs: ["pseudo"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
11413
- }
11414
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScSmartCaptchaComponent, decorators: [{
11415
- type: Component,
11416
- args: [{ standalone: true, selector: 'sc-smart-captcha', imports: [TuiLink], providers: [tuiProvide(NG_VALUE_ACCESSOR, ScSmartCaptchaComponent, true)], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n #container\n class=\"min-h-[7.375rem]\"\n [class.hidden]=\"hasError()\"\n></div>\n@if (hasError()) {\n <p\n role=\"alert\"\n class=\"text-body-s text-tui-text-negative\"\n >\n \u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443 \u00AB\u042F \u043D\u0435 \u0440\u043E\u0431\u043E\u0442\u00BB.\n <button\n tuiLink\n type=\"button\"\n (click)=\"retry()\"\n >\n \u041F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u044C\n </button>\n </p>\n}\n" }]
11417
- }], ctorParameters: () => [] });
11418
-
11419
12332
  /**
11420
12333
  * Компонент формы обратной связи.
11421
12334
  */
@@ -11487,7 +12400,7 @@ class ScFeedbackFormComponent {
11487
12400
  }
11488
12401
  }));
11489
12402
  /**
11490
- * Поле ввода токена капчи, имя `recaptcha` задано API.
12403
+ * Поле ввода для recaptcha.
11491
12404
  */
11492
12405
  this.recaptchaControl = new FormControl(null, Validators.required);
11493
12406
  /**
@@ -11529,10 +12442,8 @@ class ScFeedbackFormComponent {
11529
12442
  }),
11530
12443
  // eslint-disable-next-line rxjs/no-implicit-any-catch
11531
12444
  catchError((error) => {
11532
- // Токен капчи одноразовый: для повторной отправки нужна новая проверка.
11533
- this.recaptchaControl.reset();
11534
12445
  this.form.markAllAsTouched();
11535
- const { errors, message } = (error.error ?? {});
12446
+ const { errors, message } = error.error;
11536
12447
  if (errors) {
11537
12448
  Object.keys(errors).forEach((key) => {
11538
12449
  const control = this.form.get(key);
@@ -11569,6 +12480,16 @@ class ScFeedbackFormComponent {
11569
12480
  * Список стран.
11570
12481
  */
11571
12482
  this.countries = getCountries();
12483
+ /**
12484
+ * Это решение ошибки #4940, было @see [предложено](https://github.com/DethAriel/ng-recaptcha/issues/123#issuecomment-426112101) автором {@link RecaptchaComponent}.
12485
+ */
12486
+ RecaptchaComponent.prototype.ngOnDestroy = function () {
12487
+ // eslint-disable-next-line @typescript-eslint/dot-notation
12488
+ if (this['subscription']) {
12489
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/dot-notation
12490
+ this['subscription'].unsubscribe();
12491
+ }
12492
+ };
11572
12493
  merge(this.user$, this.accepted$.pipe(tap((files) => {
11573
12494
  this.form.controls.files.reset([]);
11574
12495
  this.onUploadFile(files);
@@ -11692,7 +12613,7 @@ class ScFeedbackFormComponent {
11692
12613
  return ScPhoneService.isValidPhoneNumberForMessenger(value);
11693
12614
  }
11694
12615
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScFeedbackFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
11695
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScFeedbackFormComponent, isStandalone: true, selector: "sc-feedback-form", inputs: { slugInput: { classPropertyName: "slugInput", publicName: "slug", isSignal: true, isRequired: false, transformFunction: null }, fieldsInput: { classPropertyName: "fieldsInput", publicName: "fields", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clickOffer: "clickOffer", sendSuccess: "sendSuccess" }, ngImport: i0, template: "@let fieldsList = fields();\n<form\n *ngIf=\"fieldsList.length > 0\"\n [formGroup]=\"form\"\n (ngSubmit)=\"onSubmit.next()\"\n class=\"flex flex-col justify-between gap-4\"\n>\n <!-- \u0424\u0418\u041E -->\n <label\n *ngIf=\"fieldsList.includes('name')\"\n tuiLabel\n >\n \u0424\u0418\u041E\n <tui-input formControlName=\"name\"> \u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u0412\u0430\u0448\u0438 \u0424.\u0418.\u041E. </tui-input>\n <tui-error\n formControlName=\"name\"\n [error]=\"[] | tuiFieldError | async\"\n />\n </label>\n\n <!-- \u0422\u0435\u043B\u0435\u0444\u043E\u043D -->\n @if (fieldsList.includes('phone')) {\n <label tuiLabel>\n \u0422\u0435\u043B\u0435\u0444\u043E\u043D\n <tui-textfield>\n <input\n formControlName=\"phone\"\n tuiInputPhoneInternational\n [countrySearch]=\"true\"\n [countries]=\"(countries | tuiSortCountries | async) || []\"\n autocomplete=\"phone\"\n />\n </tui-textfield>\n <tui-error\n formControlName=\"phone\"\n [error]=\"[] | tuiFieldError | async\"\n />\n </label>\n\n <label\n *ngIf=\"fieldsList.includes('isShouldUseMessenger') && form.controls.phone.value && isValidPhoneNumberForMessenger(form.controls.phone.value)\"\n tuiLabel\n >\n <input\n formControlName=\"isShouldUseMessenger\"\n tuiCheckbox\n type=\"checkbox\"\n />\n <span tuiTitle> \u041D\u0430\u043F\u0438\u0441\u0430\u0442\u044C \u0432 \u043C\u0435\u0441\u0441\u0435\u043D\u0434\u0436\u0435\u0440\u0430\u0445, \u0430 \u043D\u0435 \u0437\u0432\u043E\u043D\u0438\u0442\u044C </span>\n </label>\n }\n <!-- \u0410\u0434\u0440\u0435\u0441 \u044D\u043B\u0435\u043A\u0442\u0440\u043E\u043D\u043D\u043E\u0439 \u043F\u043E\u0447\u0442\u044B -->\n <label\n *ngIf=\"fieldsList.includes('email')\"\n tuiLabel\n >\n \u0410\u0434\u0440\u0435\u0441 \u044D\u043B\u0435\u043A\u0442\u0440\u043E\u043D\u043D\u043E\u0439 \u043F\u043E\u0447\u0442\u044B\n <tui-input formControlName=\"email\">\n \u0410\u0434\u0440\u0435\u0441 \u044D\u043B\u0435\u043A\u0442\u0440\u043E\u043D\u043D\u043E\u0439 \u043F\u043E\u0447\u0442\u044B\n <sc-suggestion-field\n *tuiDataList\n [type]=\"suggestionType.email\"\n />\n </tui-input>\n <tui-error\n formControlName=\"email\"\n [error]=\"[] | tuiFieldError | async\"\n />\n </label>\n\n <!-- \u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439 -->\n <label\n *ngIf=\"fieldsList.includes('message')\"\n tuiLabel\n >\n \u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439\n <tui-textarea formControlName=\"message\"> \u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439 </tui-textarea>\n <tui-error\n formControlName=\"message\"\n [error]=\"[] | tuiFieldError | async\"\n />\n </label>\n\n <!-- \u0424\u0430\u0439\u043B\u044B -->\n <div\n *ngIf=\"fieldsList.includes('files')\"\n class=\"flex flex-col gap-1\"\n >\n <label tuiInputFiles>\n <input\n #validator=\"tuiInputFilesValidator\"\n accept=\".doc,.docx,.pdf,image/*\"\n tuiInputFiles\n [formControl]=\"loadedFile\"\n [multiple]=\"true\"\n (reject)=\"onReject($event)\"\n />\n </label>\n\n <tui-error\n *ngIf=\"loadedFile.invalid\"\n [error]=\"['maxLength'] | tuiFieldError | async\"\n [formControl]=\"loadedFile\"\n />\n\n @let accepted = accepted$ | async;\n <tui-files\n *ngIf=\"accepted?.length || rejected.length\"\n class=\"tui-space_top-1\"\n >\n <tui-file\n *ngFor=\"let file of accepted\"\n [file]=\"file\"\n (remove)=\"onRemove(file)\"\n />\n <tui-file\n *ngFor=\"let file of rejected\"\n state=\"error\"\n [file]=\"(file | tuiFileRejected: validator | async) || file\"\n (remove)=\"onRemove(file)\"\n />\n </tui-files>\n </div>\n\n <!-- \u041A\u0430\u043F\u0447\u0430 -->\n @if (form.get('recaptcha'); as recaptcha) {\n <div\n class=\"mb-3 max-h-[7.5rem] overflow-hidden duration-500\"\n [ngClass]=\"{ '!max-h-0 !-m-2': recaptcha.valid }\"\n [attr.inert]=\"recaptcha.valid ? '' : null\"\n >\n <sc-smart-captcha formControlName=\"recaptcha\" />\n </div>\n @if (recaptcha.getError('serverResponse'); as serverErrors) {\n <tui-error [error]=\"serverErrors.join(' ')\" />\n }\n }\n\n <!-- \u042D\u043B\u0435\u043C\u0435\u043D\u0442\u044B \u0443\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u044F -->\n <div class=\"flex flex-wrap items-center gap-3\">\n <button\n tuiButton\n [disabled]=\"form.invalid\"\n [loading]=\"!!(loading$ | async)\"\n type=\"submit\"\n iconStart=\"@tui.sc.send\"\n class=\"self-center\"\n >\n \u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C\n </button>\n <div class=\"grow basis-60 text-body-s\">\n \u041D\u0430\u0436\u0438\u043C\u0430\u044F \u043D\u0430 \u043A\u043D\u043E\u043F\u043A\u0443, \u0432\u044B \u0441\u043E\u0433\u043B\u0430\u0448\u0430\u0435\u0442\u0435\u0441\u044C \u0441\n <button\n tuiLink\n [pseudo]=\"true\"\n type=\"button\"\n (click)=\"context?.data?.onClickOfferHandler(); clickOffer.emit()\"\n >\n \u041F\u043E\u043B\u0438\u0442\u0438\u043A\u043E\u0439 \u043A\u043E\u043D\u0444\u0438\u0434\u0435\u043D\u0446\u0438\u0430\u043B\u044C\u043D\u043E\u0441\u0442\u0438\n </button>\n </div>\n </div>\n</form>\n", dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: TuiError, selector: "tui-error", inputs: ["error"] }, { kind: "pipe", type: TuiFieldErrorPipe, name: "tuiFieldError" }, { kind: "ngmodule", type: ScFormFieldsModule }, { kind: "component", type: ScSuggestionFieldComponent, selector: "sc-suggestion-field", inputs: ["type"], outputs: ["selectedClick"] }, { kind: "ngmodule", type: TuiInputModule }, { kind: "component", type: i3.TuiInputComponent, selector: "tui-input" }, { kind: "directive", type: i3.TuiInputDirective, selector: "tui-input" }, { kind: "ngmodule", type: TuiInputPhoneModule }, { kind: "directive", type: TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }, { kind: "component", type: TuiButtonLoading, selector: "[tuiButton][loading],[tuiIconButton][loading]", inputs: ["size", "loading"] }, { kind: "directive", type: TuiLabel, selector: "label[tuiLabel]" }, { kind: "component", type: ScSmartCaptchaComponent, selector: "sc-smart-captcha" }, { kind: "directive", type: TuiLink, selector: "a[tuiLink], button[tuiLink]", inputs: ["pseudo"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: TuiTextareaModule }, { kind: "component", type: i3.TuiTextareaComponent, selector: "tui-textarea", inputs: ["rows", "maxLength", "expandable"] }, { kind: "directive", type: i3.TuiTextareaDirective, selector: "tui-textarea" }, { kind: "component", type: TuiCheckbox, selector: "input[type=\"checkbox\"][tuiCheckbox]", inputs: ["size"] }, { kind: "directive", type: TuiTitle, selector: "[tuiTitle]", inputs: ["tuiTitle"] }, { kind: "component", type: i2$1.TuiFile, selector: "tui-file,a[tuiFile],button[tuiFile]", inputs: ["file", "state", "size", "showDelete", "showSize", "leftContent"], outputs: ["remove"] }, { kind: "component", type: i2$1.TuiInputFiles, selector: "label[tuiInputFiles]" }, { kind: "component", type: i2$1.TuiFilesComponent, selector: "tui-files", inputs: ["max", "expanded"], outputs: ["expandedChange"] }, { kind: "pipe", type: i2$1.TuiFileRejectedPipe, name: "tuiFileRejected" }, { kind: "directive", type: i2$1.TuiInputFilesDirective, selector: "input[tuiInputFiles]", outputs: ["reject"] }, { kind: "component", type: TuiInputPhoneInternational, selector: "input[tuiInputPhoneInternational]", inputs: ["countrySearch", "countries", "countryIsoCode"], outputs: ["countryIsoCodeChange"] }, { kind: "component", type: i1$2.TuiTextfieldComponent, selector: "tui-textfield:not([multi])" }, { kind: "pipe", type: TuiSortCountriesPipe, name: "tuiSortCountries" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
12616
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScFeedbackFormComponent, isStandalone: true, selector: "sc-feedback-form", inputs: { slugInput: { classPropertyName: "slugInput", publicName: "slug", isSignal: true, isRequired: false, transformFunction: null }, fieldsInput: { classPropertyName: "fieldsInput", publicName: "fields", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clickOffer: "clickOffer", sendSuccess: "sendSuccess" }, ngImport: i0, template: "@let fieldsList = fields();\n<form\n *ngIf=\"fieldsList.length > 0\"\n [formGroup]=\"form\"\n (ngSubmit)=\"onSubmit.next()\"\n class=\"flex flex-col justify-between gap-4\"\n>\n <!-- \u0424\u0418\u041E -->\n <label\n *ngIf=\"fieldsList.includes('name')\"\n tuiLabel\n >\n \u0424\u0418\u041E\n <tui-input formControlName=\"name\"> \u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u0412\u0430\u0448\u0438 \u0424.\u0418.\u041E. </tui-input>\n <tui-error\n formControlName=\"name\"\n [error]=\"[] | tuiFieldError | async\"\n />\n </label>\n\n <!-- \u0422\u0435\u043B\u0435\u0444\u043E\u043D -->\n @if (fieldsList.includes('phone')) {\n <label tuiLabel>\n \u0422\u0435\u043B\u0435\u0444\u043E\u043D\n <tui-textfield>\n <input\n formControlName=\"phone\"\n tuiInputPhoneInternational\n [countrySearch]=\"true\"\n [countries]=\"(countries | tuiSortCountries | async) || []\"\n autocomplete=\"phone\"\n />\n </tui-textfield>\n <tui-error\n formControlName=\"phone\"\n [error]=\"[] | tuiFieldError | async\"\n />\n </label>\n\n <label\n *ngIf=\"fieldsList.includes('isShouldUseMessenger') && form.controls.phone.value && isValidPhoneNumberForMessenger(form.controls.phone.value)\"\n tuiLabel\n >\n <input\n formControlName=\"isShouldUseMessenger\"\n tuiCheckbox\n type=\"checkbox\"\n />\n <span tuiTitle> \u041D\u0430\u043F\u0438\u0441\u0430\u0442\u044C \u0432 \u043C\u0435\u0441\u0441\u0435\u043D\u0434\u0436\u0435\u0440\u0430\u0445, \u0430 \u043D\u0435 \u0437\u0432\u043E\u043D\u0438\u0442\u044C </span>\n </label>\n }\n <!-- \u0410\u0434\u0440\u0435\u0441 \u044D\u043B\u0435\u043A\u0442\u0440\u043E\u043D\u043D\u043E\u0439 \u043F\u043E\u0447\u0442\u044B -->\n <label\n *ngIf=\"fieldsList.includes('email')\"\n tuiLabel\n >\n \u0410\u0434\u0440\u0435\u0441 \u044D\u043B\u0435\u043A\u0442\u0440\u043E\u043D\u043D\u043E\u0439 \u043F\u043E\u0447\u0442\u044B\n <tui-input formControlName=\"email\">\n \u0410\u0434\u0440\u0435\u0441 \u044D\u043B\u0435\u043A\u0442\u0440\u043E\u043D\u043D\u043E\u0439 \u043F\u043E\u0447\u0442\u044B\n <sc-suggestion-field\n *tuiDataList\n [type]=\"suggestionType.email\"\n />\n </tui-input>\n <tui-error\n formControlName=\"email\"\n [error]=\"[] | tuiFieldError | async\"\n />\n </label>\n\n <!-- \u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439 -->\n <label\n *ngIf=\"fieldsList.includes('message')\"\n tuiLabel\n >\n \u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439\n <tui-textarea formControlName=\"message\"> \u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439 </tui-textarea>\n <tui-error\n formControlName=\"message\"\n [error]=\"[] | tuiFieldError | async\"\n />\n </label>\n\n <!-- \u0424\u0430\u0439\u043B\u044B -->\n <div\n *ngIf=\"fieldsList.includes('files')\"\n class=\"flex flex-col gap-1\"\n >\n <label tuiInputFiles>\n <input\n #validator=\"tuiInputFilesValidator\"\n accept=\".doc,.docx,.pdf,image/*\"\n tuiInputFiles\n [formControl]=\"loadedFile\"\n [multiple]=\"true\"\n (reject)=\"onReject($event)\"\n />\n </label>\n\n <tui-error\n *ngIf=\"loadedFile.invalid\"\n [error]=\"['maxLength'] | tuiFieldError | async\"\n [formControl]=\"loadedFile\"\n />\n\n @let accepted = accepted$ | async;\n <tui-files\n *ngIf=\"accepted?.length || rejected.length\"\n class=\"tui-space_top-1\"\n >\n <tui-file\n *ngFor=\"let file of accepted\"\n [file]=\"file\"\n (remove)=\"onRemove(file)\"\n />\n <tui-file\n *ngFor=\"let file of rejected\"\n state=\"error\"\n [file]=\"(file | tuiFileRejected: validator | async) || file\"\n (remove)=\"onRemove(file)\"\n />\n </tui-files>\n </div>\n\n <!-- recaptcha -->\n @if (form.get('recaptcha')) {\n <div\n class=\"mb-3 max-h-20 overflow-hidden duration-500\"\n [ngClass]=\"{ '!max-h-0 !-m-2': form.get('recaptcha')?.valid }\"\n >\n <re-captcha formControlName=\"recaptcha\" />\n </div>\n }\n\n <!-- \u042D\u043B\u0435\u043C\u0435\u043D\u0442\u044B \u0443\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u044F -->\n <div class=\"flex flex-wrap items-center gap-3\">\n <button\n tuiButton\n [disabled]=\"form.invalid\"\n [loading]=\"!!(loading$ | async)\"\n type=\"submit\"\n iconStart=\"@tui.sc.send\"\n class=\"self-center\"\n >\n \u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C\n </button>\n <div class=\"grow basis-60 text-body-s\">\n \u041D\u0430\u0436\u0438\u043C\u0430\u044F \u043D\u0430 \u043A\u043D\u043E\u043F\u043A\u0443, \u0432\u044B \u0441\u043E\u0433\u043B\u0430\u0448\u0430\u0435\u0442\u0435\u0441\u044C \u0441\n <button\n tuiLink\n [pseudo]=\"true\"\n type=\"button\"\n (click)=\"context?.data?.onClickOfferHandler(); clickOffer.emit()\"\n >\n \u041F\u043E\u043B\u0438\u0442\u0438\u043A\u043E\u0439 \u043A\u043E\u043D\u0444\u0438\u0434\u0435\u043D\u0446\u0438\u0430\u043B\u044C\u043D\u043E\u0441\u0442\u0438\n </button>\n </div>\n </div>\n</form>\n", dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: TuiError, selector: "tui-error", inputs: ["error"] }, { kind: "pipe", type: TuiFieldErrorPipe, name: "tuiFieldError" }, { kind: "ngmodule", type: ScFormFieldsModule }, { kind: "component", type: ScSuggestionFieldComponent, selector: "sc-suggestion-field", inputs: ["type"], outputs: ["selectedClick"] }, { kind: "ngmodule", type: TuiInputModule }, { kind: "component", type: i3.TuiInputComponent, selector: "tui-input" }, { kind: "directive", type: i3.TuiInputDirective, selector: "tui-input" }, { kind: "ngmodule", type: TuiInputPhoneModule }, { kind: "directive", type: TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }, { kind: "component", type: TuiButtonLoading, selector: "[tuiButton][loading],[tuiIconButton][loading]", inputs: ["size", "loading"] }, { kind: "directive", type: TuiLabel, selector: "label[tuiLabel]" }, { kind: "ngmodule", type: RecaptchaFormsModule }, { kind: "directive", type: i4$3.RecaptchaValueAccessorDirective, selector: "re-captcha[formControlName],re-captcha[formControl],re-captcha[ngModel]" }, { kind: "ngmodule", type: RecaptchaModule }, { kind: "component", type: i4$3.RecaptchaComponent, selector: "re-captcha", inputs: ["id", "siteKey", "theme", "type", "size", "tabIndex", "badge", "errorMode"], outputs: ["resolved", "error", "errored"], exportAs: ["reCaptcha"] }, { kind: "directive", type: TuiLink, selector: "a[tuiLink], button[tuiLink]", inputs: ["pseudo"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: TuiTextareaModule }, { kind: "component", type: i3.TuiTextareaComponent, selector: "tui-textarea", inputs: ["rows", "maxLength", "expandable"] }, { kind: "directive", type: i3.TuiTextareaDirective, selector: "tui-textarea" }, { kind: "component", type: TuiCheckbox, selector: "input[type=\"checkbox\"][tuiCheckbox]", inputs: ["size"] }, { kind: "directive", type: TuiTitle, selector: "[tuiTitle]", inputs: ["tuiTitle"] }, { kind: "component", type: i2$1.TuiFile, selector: "tui-file,a[tuiFile],button[tuiFile]", inputs: ["file", "state", "size", "showDelete", "showSize", "leftContent"], outputs: ["remove"] }, { kind: "component", type: i2$1.TuiInputFiles, selector: "label[tuiInputFiles]" }, { kind: "component", type: i2$1.TuiFilesComponent, selector: "tui-files", inputs: ["max", "expanded"], outputs: ["expandedChange"] }, { kind: "pipe", type: i2$1.TuiFileRejectedPipe, name: "tuiFileRejected" }, { kind: "directive", type: i2$1.TuiInputFilesDirective, selector: "input[tuiInputFiles]", outputs: ["reject"] }, { kind: "component", type: TuiInputPhoneInternational, selector: "input[tuiInputPhoneInternational]", inputs: ["countrySearch", "countries", "countryIsoCode"], outputs: ["countryIsoCodeChange"] }, { kind: "component", type: i1$2.TuiTextfieldComponent, selector: "tui-textfield:not([multi])" }, { kind: "pipe", type: TuiSortCountriesPipe, name: "tuiSortCountries" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
11696
12617
  }
11697
12618
  __decorate([
11698
12619
  tuiPure
@@ -11712,7 +12633,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
11712
12633
  TuiButton,
11713
12634
  TuiButtonLoading,
11714
12635
  TuiLabel,
11715
- ScSmartCaptchaComponent,
12636
+ RecaptchaFormsModule,
12637
+ RecaptchaModule,
11716
12638
  TuiLink,
11717
12639
  NgIf,
11718
12640
  TuiTextareaModule,
@@ -11722,7 +12644,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
11722
12644
  TuiInputPhoneInternational,
11723
12645
  ...TuiTextfield,
11724
12646
  TuiSortCountriesPipe,
11725
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let fieldsList = fields();\n<form\n *ngIf=\"fieldsList.length > 0\"\n [formGroup]=\"form\"\n (ngSubmit)=\"onSubmit.next()\"\n class=\"flex flex-col justify-between gap-4\"\n>\n <!-- \u0424\u0418\u041E -->\n <label\n *ngIf=\"fieldsList.includes('name')\"\n tuiLabel\n >\n \u0424\u0418\u041E\n <tui-input formControlName=\"name\"> \u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u0412\u0430\u0448\u0438 \u0424.\u0418.\u041E. </tui-input>\n <tui-error\n formControlName=\"name\"\n [error]=\"[] | tuiFieldError | async\"\n />\n </label>\n\n <!-- \u0422\u0435\u043B\u0435\u0444\u043E\u043D -->\n @if (fieldsList.includes('phone')) {\n <label tuiLabel>\n \u0422\u0435\u043B\u0435\u0444\u043E\u043D\n <tui-textfield>\n <input\n formControlName=\"phone\"\n tuiInputPhoneInternational\n [countrySearch]=\"true\"\n [countries]=\"(countries | tuiSortCountries | async) || []\"\n autocomplete=\"phone\"\n />\n </tui-textfield>\n <tui-error\n formControlName=\"phone\"\n [error]=\"[] | tuiFieldError | async\"\n />\n </label>\n\n <label\n *ngIf=\"fieldsList.includes('isShouldUseMessenger') && form.controls.phone.value && isValidPhoneNumberForMessenger(form.controls.phone.value)\"\n tuiLabel\n >\n <input\n formControlName=\"isShouldUseMessenger\"\n tuiCheckbox\n type=\"checkbox\"\n />\n <span tuiTitle> \u041D\u0430\u043F\u0438\u0441\u0430\u0442\u044C \u0432 \u043C\u0435\u0441\u0441\u0435\u043D\u0434\u0436\u0435\u0440\u0430\u0445, \u0430 \u043D\u0435 \u0437\u0432\u043E\u043D\u0438\u0442\u044C </span>\n </label>\n }\n <!-- \u0410\u0434\u0440\u0435\u0441 \u044D\u043B\u0435\u043A\u0442\u0440\u043E\u043D\u043D\u043E\u0439 \u043F\u043E\u0447\u0442\u044B -->\n <label\n *ngIf=\"fieldsList.includes('email')\"\n tuiLabel\n >\n \u0410\u0434\u0440\u0435\u0441 \u044D\u043B\u0435\u043A\u0442\u0440\u043E\u043D\u043D\u043E\u0439 \u043F\u043E\u0447\u0442\u044B\n <tui-input formControlName=\"email\">\n \u0410\u0434\u0440\u0435\u0441 \u044D\u043B\u0435\u043A\u0442\u0440\u043E\u043D\u043D\u043E\u0439 \u043F\u043E\u0447\u0442\u044B\n <sc-suggestion-field\n *tuiDataList\n [type]=\"suggestionType.email\"\n />\n </tui-input>\n <tui-error\n formControlName=\"email\"\n [error]=\"[] | tuiFieldError | async\"\n />\n </label>\n\n <!-- \u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439 -->\n <label\n *ngIf=\"fieldsList.includes('message')\"\n tuiLabel\n >\n \u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439\n <tui-textarea formControlName=\"message\"> \u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439 </tui-textarea>\n <tui-error\n formControlName=\"message\"\n [error]=\"[] | tuiFieldError | async\"\n />\n </label>\n\n <!-- \u0424\u0430\u0439\u043B\u044B -->\n <div\n *ngIf=\"fieldsList.includes('files')\"\n class=\"flex flex-col gap-1\"\n >\n <label tuiInputFiles>\n <input\n #validator=\"tuiInputFilesValidator\"\n accept=\".doc,.docx,.pdf,image/*\"\n tuiInputFiles\n [formControl]=\"loadedFile\"\n [multiple]=\"true\"\n (reject)=\"onReject($event)\"\n />\n </label>\n\n <tui-error\n *ngIf=\"loadedFile.invalid\"\n [error]=\"['maxLength'] | tuiFieldError | async\"\n [formControl]=\"loadedFile\"\n />\n\n @let accepted = accepted$ | async;\n <tui-files\n *ngIf=\"accepted?.length || rejected.length\"\n class=\"tui-space_top-1\"\n >\n <tui-file\n *ngFor=\"let file of accepted\"\n [file]=\"file\"\n (remove)=\"onRemove(file)\"\n />\n <tui-file\n *ngFor=\"let file of rejected\"\n state=\"error\"\n [file]=\"(file | tuiFileRejected: validator | async) || file\"\n (remove)=\"onRemove(file)\"\n />\n </tui-files>\n </div>\n\n <!-- \u041A\u0430\u043F\u0447\u0430 -->\n @if (form.get('recaptcha'); as recaptcha) {\n <div\n class=\"mb-3 max-h-[7.5rem] overflow-hidden duration-500\"\n [ngClass]=\"{ '!max-h-0 !-m-2': recaptcha.valid }\"\n [attr.inert]=\"recaptcha.valid ? '' : null\"\n >\n <sc-smart-captcha formControlName=\"recaptcha\" />\n </div>\n @if (recaptcha.getError('serverResponse'); as serverErrors) {\n <tui-error [error]=\"serverErrors.join(' ')\" />\n }\n }\n\n <!-- \u042D\u043B\u0435\u043C\u0435\u043D\u0442\u044B \u0443\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u044F -->\n <div class=\"flex flex-wrap items-center gap-3\">\n <button\n tuiButton\n [disabled]=\"form.invalid\"\n [loading]=\"!!(loading$ | async)\"\n type=\"submit\"\n iconStart=\"@tui.sc.send\"\n class=\"self-center\"\n >\n \u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C\n </button>\n <div class=\"grow basis-60 text-body-s\">\n \u041D\u0430\u0436\u0438\u043C\u0430\u044F \u043D\u0430 \u043A\u043D\u043E\u043F\u043A\u0443, \u0432\u044B \u0441\u043E\u0433\u043B\u0430\u0448\u0430\u0435\u0442\u0435\u0441\u044C \u0441\n <button\n tuiLink\n [pseudo]=\"true\"\n type=\"button\"\n (click)=\"context?.data?.onClickOfferHandler(); clickOffer.emit()\"\n >\n \u041F\u043E\u043B\u0438\u0442\u0438\u043A\u043E\u0439 \u043A\u043E\u043D\u0444\u0438\u0434\u0435\u043D\u0446\u0438\u0430\u043B\u044C\u043D\u043E\u0441\u0442\u0438\n </button>\n </div>\n </div>\n</form>\n" }]
12647
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let fieldsList = fields();\n<form\n *ngIf=\"fieldsList.length > 0\"\n [formGroup]=\"form\"\n (ngSubmit)=\"onSubmit.next()\"\n class=\"flex flex-col justify-between gap-4\"\n>\n <!-- \u0424\u0418\u041E -->\n <label\n *ngIf=\"fieldsList.includes('name')\"\n tuiLabel\n >\n \u0424\u0418\u041E\n <tui-input formControlName=\"name\"> \u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u0412\u0430\u0448\u0438 \u0424.\u0418.\u041E. </tui-input>\n <tui-error\n formControlName=\"name\"\n [error]=\"[] | tuiFieldError | async\"\n />\n </label>\n\n <!-- \u0422\u0435\u043B\u0435\u0444\u043E\u043D -->\n @if (fieldsList.includes('phone')) {\n <label tuiLabel>\n \u0422\u0435\u043B\u0435\u0444\u043E\u043D\n <tui-textfield>\n <input\n formControlName=\"phone\"\n tuiInputPhoneInternational\n [countrySearch]=\"true\"\n [countries]=\"(countries | tuiSortCountries | async) || []\"\n autocomplete=\"phone\"\n />\n </tui-textfield>\n <tui-error\n formControlName=\"phone\"\n [error]=\"[] | tuiFieldError | async\"\n />\n </label>\n\n <label\n *ngIf=\"fieldsList.includes('isShouldUseMessenger') && form.controls.phone.value && isValidPhoneNumberForMessenger(form.controls.phone.value)\"\n tuiLabel\n >\n <input\n formControlName=\"isShouldUseMessenger\"\n tuiCheckbox\n type=\"checkbox\"\n />\n <span tuiTitle> \u041D\u0430\u043F\u0438\u0441\u0430\u0442\u044C \u0432 \u043C\u0435\u0441\u0441\u0435\u043D\u0434\u0436\u0435\u0440\u0430\u0445, \u0430 \u043D\u0435 \u0437\u0432\u043E\u043D\u0438\u0442\u044C </span>\n </label>\n }\n <!-- \u0410\u0434\u0440\u0435\u0441 \u044D\u043B\u0435\u043A\u0442\u0440\u043E\u043D\u043D\u043E\u0439 \u043F\u043E\u0447\u0442\u044B -->\n <label\n *ngIf=\"fieldsList.includes('email')\"\n tuiLabel\n >\n \u0410\u0434\u0440\u0435\u0441 \u044D\u043B\u0435\u043A\u0442\u0440\u043E\u043D\u043D\u043E\u0439 \u043F\u043E\u0447\u0442\u044B\n <tui-input formControlName=\"email\">\n \u0410\u0434\u0440\u0435\u0441 \u044D\u043B\u0435\u043A\u0442\u0440\u043E\u043D\u043D\u043E\u0439 \u043F\u043E\u0447\u0442\u044B\n <sc-suggestion-field\n *tuiDataList\n [type]=\"suggestionType.email\"\n />\n </tui-input>\n <tui-error\n formControlName=\"email\"\n [error]=\"[] | tuiFieldError | async\"\n />\n </label>\n\n <!-- \u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439 -->\n <label\n *ngIf=\"fieldsList.includes('message')\"\n tuiLabel\n >\n \u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439\n <tui-textarea formControlName=\"message\"> \u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439 </tui-textarea>\n <tui-error\n formControlName=\"message\"\n [error]=\"[] | tuiFieldError | async\"\n />\n </label>\n\n <!-- \u0424\u0430\u0439\u043B\u044B -->\n <div\n *ngIf=\"fieldsList.includes('files')\"\n class=\"flex flex-col gap-1\"\n >\n <label tuiInputFiles>\n <input\n #validator=\"tuiInputFilesValidator\"\n accept=\".doc,.docx,.pdf,image/*\"\n tuiInputFiles\n [formControl]=\"loadedFile\"\n [multiple]=\"true\"\n (reject)=\"onReject($event)\"\n />\n </label>\n\n <tui-error\n *ngIf=\"loadedFile.invalid\"\n [error]=\"['maxLength'] | tuiFieldError | async\"\n [formControl]=\"loadedFile\"\n />\n\n @let accepted = accepted$ | async;\n <tui-files\n *ngIf=\"accepted?.length || rejected.length\"\n class=\"tui-space_top-1\"\n >\n <tui-file\n *ngFor=\"let file of accepted\"\n [file]=\"file\"\n (remove)=\"onRemove(file)\"\n />\n <tui-file\n *ngFor=\"let file of rejected\"\n state=\"error\"\n [file]=\"(file | tuiFileRejected: validator | async) || file\"\n (remove)=\"onRemove(file)\"\n />\n </tui-files>\n </div>\n\n <!-- recaptcha -->\n @if (form.get('recaptcha')) {\n <div\n class=\"mb-3 max-h-20 overflow-hidden duration-500\"\n [ngClass]=\"{ '!max-h-0 !-m-2': form.get('recaptcha')?.valid }\"\n >\n <re-captcha formControlName=\"recaptcha\" />\n </div>\n }\n\n <!-- \u042D\u043B\u0435\u043C\u0435\u043D\u0442\u044B \u0443\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u044F -->\n <div class=\"flex flex-wrap items-center gap-3\">\n <button\n tuiButton\n [disabled]=\"form.invalid\"\n [loading]=\"!!(loading$ | async)\"\n type=\"submit\"\n iconStart=\"@tui.sc.send\"\n class=\"self-center\"\n >\n \u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C\n </button>\n <div class=\"grow basis-60 text-body-s\">\n \u041D\u0430\u0436\u0438\u043C\u0430\u044F \u043D\u0430 \u043A\u043D\u043E\u043F\u043A\u0443, \u0432\u044B \u0441\u043E\u0433\u043B\u0430\u0448\u0430\u0435\u0442\u0435\u0441\u044C \u0441\n <button\n tuiLink\n [pseudo]=\"true\"\n type=\"button\"\n (click)=\"context?.data?.onClickOfferHandler(); clickOffer.emit()\"\n >\n \u041F\u043E\u043B\u0438\u0442\u0438\u043A\u043E\u0439 \u043A\u043E\u043D\u0444\u0438\u0434\u0435\u043D\u0446\u0438\u0430\u043B\u044C\u043D\u043E\u0441\u0442\u0438\n </button>\n </div>\n </div>\n</form>\n" }]
11726
12648
  }], ctorParameters: () => [], propDecorators: { isValidPhoneNumberForMessenger: [] } });
11727
12649
 
11728
12650
  /**
@@ -12309,5 +13231,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
12309
13231
  * Generated bundle index. Do not edit.
12310
13232
  */
12311
13233
 
12312
- export { AbstractScPriceCard, AuthMethod, CURRENT_COUNTRY_ID, CostWithDiscountComponent, FilesAndDocumentsComponent, FilesAndDocumentsModule, FinishDateTimeTransformerDirective, IDLE_ITEM_STATE, IS_DEFAULT_COUNTRY, MAX_FILES_IN_FORM_INPUT, SC_ALLOW_SELECT_TERMINATED, SC_BANNER_DURATION, SC_BLOCK_INCOMPLETE_ADDRESS, SC_CATALOG_FILTER_PREVIEWS, SC_CATALOG_PRODUCTS_FILTERS, SC_CATALOG_SHOW_PRODUCTS_RECURSIVELY, SC_DATE_FORMATTER, SC_DEBOUNCE_TIME_DEFAULT, SC_DIALOG_SERVICE_TOKEN, SC_ERROR_CHANGE_HANDLER, SC_FAQ_GROUP_QUERY_PARAM, SC_FAQ_QUESTION_ANCHOR_PREFIX, SC_HELP_NOTIFICATION_CLOSE, SC_HELP_NOTIFICATION_LIMIT, SC_HIDDEN_PRINT_ELEMENTS, SC_MANAGER_QR_HANDLER, SC_NOTIFY_WHEN_IN_STOCK_REQUIRED_FIELDS, SC_ORDER_FORM_CONFIG, SC_ORDER_OPTIONS, SC_PAGE_SIZE_OPTIONS$1 as SC_PAGE_SIZE_OPTIONS, SC_SHOW_HELP_NOTIFICATION_IN_PHONE_INPUT, SC_SMART_CAPTCHA_LOAD_TIMEOUT, SC_SMART_CAPTCHA_SETTINGS, SC_USER_CITY_INFO, SC_USER_INFO, SC_USER_PROVIDERS, SC_VERIFICATION_CODE_TIMEOUT, ScAccordionComponent, ScAccordionContentDirective, ScAccordionModule, ScAddContactDialogComponent, ScAddContragentBankAccountsDialogComponent, ScAddContragentDialogComponent, ScAddDeliveryAddressDialogComponent, ScAddOrEditingCartItemDialogComponent, ScAddOrEditingCartItemFormComponent, ScAddressQualityDirective, ScAddressQualityService, ScAddressesSelectionFieldComponent, ScAuthModule, ScBannerComponent, ScBannerModule, ScBrandsListComponent, ScBrandsListModule, ScCartAddProductsFromCsvDialogComponent, ScCatalogFilterCollapsibleComponent, ScCatalogFiltersComponent, ScCatalogModule, ScCategoryCardComponent, ScContactsAccordionComponent, ScContactsModule, ScContragentsAccordionComponent, ScContragentsAccordionItemComponent, ScContragentsModule, ScDeliveryAddressAccordionComponent, ScDeliveryAddressAccordionItemComponent, ScDeliveryAddressModule, ScDownloadPriceListComponent, ScDraftComponent, ScEmailLinkDirective, ScErrorBlockStatusComponent, ScErrorHandlerComponent, ScFavoriteButtonComponent, ScFeedbackFormComponent, ScFocusFirstInvalidFieldDirective, ScFormFieldsModule, ScFormatDatePipe, ScFrequentlyAskedQuestionsComponent, ScFrequentlyAskedQuestionsGroupSelectorComponent, ScFrequentlyAskedQuestionsWithGroupsComponent, ScGratitudeComponent, ScHelpNotificationService, ScHoverImageCarouselComponent, ScInputQuantityComponent, ScLinks, ScManagerCardComponent, ScManagerCardPushComponent, ScNewContactFormComponent, ScNewContragentBankAccountsFormComponent, ScNewContragentFormComponent, ScNewOrderItemsComponent, ScNewsCardComponent, ScNewsCardSkeletonComponent, ScNewsListComponent, ScNewsModule, ScNextInputFocusDirective, ScNextInputFocusModule, ScNoindexDirective, ScNoindexWrapperComponent, ScNotifyWhenInStockDialogComponent, ScOrderAccessorDirective, ScOrderFormComponent, ScOrderItemComponent, ScOrderItemsListByStockComponent, ScOrderItemsListComponent, ScOrderItemsListWithActionsComponent, ScOrderModule, ScPaginationLinksComponent, ScPaymentStatusComponent, ScPersonalDataProcessingPolicyComponent, ScPhoneFormatPipe, ScPreviewSampleComponent, ScPreviewSampleModule, ScPreviewSamplesMosquitoComponent, ScPriceCardComponent, ScPriceCardInlineComponent, ScPriceHistoryComponent, ScPriceListPaginationComponent, ScPriceWarehouseStockComponent, ScPrintDirective, ScPrintService, ScPrivacyPolicyComponent, ScProductInAllWarehousesPipe, ScProfileAccordionsContentComponent, ScProfileModule, ScPublicOfferComponent, ScQRCodeDialogComponent, ScQRCodeModule, ScResetUserPasswordComponent, ScResourcePreviewComponent, ScSandwichComponent, ScSandwichM2Component, ScSandwichM2SkeletonComponent, ScSandwichSkeletonComponent, ScScriptsService, ScSelectOnFocusinDirective, ScSeoTagsComponent, ScShareButtonComponent, ScShareDialogComponent, ScShareMessengersLinksComponent, ScShareService, ScSignInFormByEmailComponent, ScSignInFormByPhoneComponent, ScSignInFormComponent, ScSignUpFormComponent, ScSimpleSignUpFormComponent, ScSmartCaptchaComponent, ScSmartCaptchaLoaderService, ScSuggestionFieldComponent, ScTelLinkDirective, ScTerminalLinkDirective, ScUpdateUserInfoDialogComponent, ScUserManagersComponent, ScUserModule, ScUserPhoneApproveDialogComponent, ScVerificationModule, ScVerificationPhoneCheckFormComponent, TreeDirective, TreeIconService, TreeLoaderService, TreeTopDirective, phoneValidator, scAtLeastOneRequiredValidator, scBicValidator, scClientUiIconsName, scCorrespondentAccountValidator, scGetCurrentRoute, scGetFaqGroupQueryParams, scGetFaqQuestionAnchor, scGetFaqQuestionIdFromAnchor, scPasswordConfirmMatchingValidator, stepValidator, tuiDateValueTransformerDefaultProvider };
13234
+ export { AbstractScPriceCard, AuthMethod, CURRENT_COUNTRY_ID, CostWithDiscountComponent, FilesAndDocumentsComponent, FilesAndDocumentsModule, FinishDateTimeTransformerDirective, IDLE_ITEM_STATE, IS_DEFAULT_COUNTRY, MAX_FILES_IN_FORM_INPUT, SC_ALLOW_SELECT_TERMINATED, SC_BANNER_DURATION, SC_BLOCK_INCOMPLETE_ADDRESS, SC_CATALOG_FILTER_PREVIEWS, SC_CATALOG_PRODUCTS_FILTERS, SC_CATALOG_SHOW_PRODUCTS_RECURSIVELY, SC_DATE_FORMATTER, SC_DEBOUNCE_TIME_DEFAULT, SC_DIALOG_SERVICE_TOKEN, SC_ERROR_CHANGE_HANDLER, SC_FAQ_GROUP_QUERY_PARAM, SC_FAQ_QUESTION_ANCHOR_PREFIX, SC_HELP_NOTIFICATION_CLOSE, SC_HELP_NOTIFICATION_LIMIT, SC_HIDDEN_PRINT_ELEMENTS, SC_MANAGER_QR_HANDLER, SC_NOTIFY_WHEN_IN_STOCK_REQUIRED_FIELDS, SC_ORDER_FORM_CONFIG, SC_ORDER_OPTIONS, SC_PAGE_SIZE_OPTIONS$1 as SC_PAGE_SIZE_OPTIONS, SC_SHOW_HELP_NOTIFICATION_IN_PHONE_INPUT, SC_USER_CITY_INFO, SC_USER_INFO, SC_USER_PROVIDERS, SC_VERIFICATION_CODE_TIMEOUT, ScAccordionComponent, ScAccordionContentDirective, ScAccordionModule, ScAddContactDialogComponent, ScAddContragentBankAccountsDialogComponent, ScAddContragentDialogComponent, ScAddDeliveryAddressDialogComponent, ScAddOrEditingCartItemDialogComponent, ScAddOrEditingCartItemFormComponent, ScAddressQualityDirective, ScAddressQualityService, ScAddressesSelectionFieldComponent, ScAuthModule, ScBannerComponent, ScBannerModule, ScBrandsListComponent, ScBrandsListModule, ScCartAddProductsFromCsvDialogComponent, ScCatalogFilterCollapsibleComponent, ScCatalogFiltersComponent, ScCatalogModule, ScCategoryCardComponent, ScConfirmAlertComponent, ScContactsAccordionComponent, ScContactsModule, ScContragentsAccordionComponent, ScContragentsAccordionItemComponent, ScContragentsModule, ScDeliveryAddressAccordionComponent, ScDeliveryAddressAccordionItemComponent, ScDeliveryAddressModule, ScDownloadPriceListComponent, ScDraftComponent, ScDraftExpirationClockService, ScDraftListHelper, ScDraftListUpdatesService, ScEmailLinkDirective, ScErrorBlockStatusComponent, ScErrorHandlerComponent, ScFavoriteButtonComponent, ScFeedbackFormComponent, ScFocusFirstInvalidFieldDirective, ScFormFieldsModule, ScFormatDatePipe, ScFrequentlyAskedQuestionsComponent, ScFrequentlyAskedQuestionsGroupSelectorComponent, ScFrequentlyAskedQuestionsWithGroupsComponent, ScGratitudeComponent, ScHelpNotificationService, ScHoverImageCarouselComponent, ScInputQuantityComponent, ScLinks, ScManagerCardComponent, ScManagerCardPushComponent, ScNewContactFormComponent, ScNewContragentBankAccountsFormComponent, ScNewContragentFormComponent, ScNewOrderItemsComponent, ScNewsCardComponent, ScNewsCardSkeletonComponent, ScNewsListComponent, ScNewsModule, ScNextInputFocusDirective, ScNextInputFocusModule, ScNoindexDirective, ScNoindexWrapperComponent, ScNotifyWhenInStockDialogComponent, ScOrderAccessorDirective, ScOrderFormComponent, ScOrderItemComponent, ScOrderItemsListByStockComponent, ScOrderItemsListComponent, ScOrderItemsListWithActionsComponent, ScOrderModule, ScPaginationLinksComponent, ScPaymentStatusComponent, ScPersonalDataProcessingPolicyComponent, ScPhoneFormatPipe, ScPreviewSampleComponent, ScPreviewSampleModule, ScPreviewSamplesMosquitoComponent, ScPriceCardComponent, ScPriceCardInlineComponent, ScPriceHistoryComponent, ScPriceListPaginationComponent, ScPriceWarehouseStockComponent, ScPrintDirective, ScPrintService, ScPrivacyPolicyComponent, ScProductInAllWarehousesPipe, ScProfileAccordionsContentComponent, ScProfileModule, ScProfstroyActualizationPanelComponent, ScProfstroyBadgeComponent, ScProfstroyDraftBadgesComponent, ScProfstroyDraftCreateDialogComponent, ScProfstroyDraftHelper, ScProfstroyDraftState, ScPublicOfferComponent, ScQRCodeDialogComponent, ScQRCodeModule, ScResetUserPasswordComponent, ScResourcePreviewComponent, ScSandwichComponent, ScSandwichM2Component, ScSandwichM2SkeletonComponent, ScSandwichSkeletonComponent, ScSelectOnFocusinDirective, ScSeoTagsComponent, ScShareButtonComponent, ScShareDialogComponent, ScShareMessengersLinksComponent, ScShareService, ScSignInFormByEmailComponent, ScSignInFormByPhoneComponent, ScSignInFormComponent, ScSignUpFormComponent, ScSimpleSignUpFormComponent, ScSuggestionFieldComponent, ScTelLinkDirective, ScTerminalLinkDirective, ScUpdateUserInfoDialogComponent, ScUserManagersComponent, ScUserModule, ScUserPhoneApproveDialogComponent, ScVerificationModule, ScVerificationPhoneCheckFormComponent, TreeDirective, TreeIconService, TreeLoaderService, TreeTopDirective, cleanupRecaptchaArtifacts, phoneValidator, scAtLeastOneRequiredValidator, scBicValidator, scClientUiIconsName, scCorrespondentAccountValidator, scGetCurrentRoute, scGetFaqGroupQueryParams, scGetFaqQuestionAnchor, scGetFaqQuestionIdFromAnchor, scPasswordConfirmMatchingValidator, stepValidator, tuiDateValueTransformerDefaultProvider };
12313
13235
  //# sourceMappingURL=snabcentr-client-ui.mjs.map