@almatar/smart-booking-dashboard 0.0.2-alpha.2 → 0.0.2-alpha.3

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 (50) hide show
  1. package/esm2022/lib/components/almatar-aside/aside.component.mjs +6 -7
  2. package/esm2022/lib/components/hotel-cancel/almatar-hotel-auto-cancellation.component.mjs +2 -2
  3. package/esm2022/lib/components/hotel-cancel/components/almatar-hotel-auto-cancellation-select-rooms/components/almatar-hotel-auto-cancellation-non-refundable-room/almatar-hotel-auto-cancellation-non-refundable-room.component.mjs +2 -2
  4. package/esm2022/lib/components/hotel-cancel/services/almatar-hotel-auto-cancellation-endpoints.service.mjs +13 -4
  5. package/esm2022/lib/components/reissue-dialog/reissue-dialog.component.mjs +135 -0
  6. package/esm2022/lib/components/reissue-dialog/reissue-upgrade.store.mjs +305 -0
  7. package/esm2022/lib/components/reissue-dialog/reissue.store.mjs +349 -0
  8. package/esm2022/lib/components/reissue-dialog/steps/reissue-date-step/reissue-date-step.component.mjs +45 -0
  9. package/esm2022/lib/components/reissue-dialog/steps/reissue-legs-step/reissue-legs-step.component.mjs +140 -0
  10. package/esm2022/lib/components/reissue-dialog/steps/reissue-notice/reissue-notice.component.mjs +16 -0
  11. package/esm2022/lib/components/reissue-dialog/steps/reissue-route-step/reissue-route-step.component.mjs +104 -0
  12. package/esm2022/lib/components/reissue-dialog/steps/reissue-service-step/reissue-service-step.component.mjs +44 -0
  13. package/esm2022/lib/components/reissue-dialog/steps/reissue-upgrade-confirm-step/reissue-upgrade-confirm-step.component.mjs +193 -0
  14. package/esm2022/lib/components/reissue-dialog/steps/reissue-upgrade-step/reissue-upgrade-step.component.mjs +88 -0
  15. package/esm2022/lib/components/workflow-actions/workflow-actions.component.mjs +5 -3
  16. package/esm2022/lib/config/dashboard.config.mjs +3 -1
  17. package/esm2022/lib/models/reissue.types.mjs +2 -0
  18. package/esm2022/lib/pages/smart-booking-dashboard/smart-booking-dashboard.component.mjs +9 -3
  19. package/esm2022/lib/services/reissue.service.mjs +142 -0
  20. package/esm2022/lib/services/toast.service.mjs +15 -7
  21. package/esm2022/lib/utils/reissue-format.mjs +64 -0
  22. package/esm2022/lib/utils/reissue-url.builder.mjs +314 -0
  23. package/esm2022/public-api.mjs +3 -1
  24. package/fesm2022/almatar-smart-booking-dashboard.mjs +1919 -25
  25. package/fesm2022/almatar-smart-booking-dashboard.mjs.map +1 -1
  26. package/lib/components/action-dialog/action-dialog.component.d.ts +1 -1
  27. package/lib/components/almatar-aside/aside.component.d.ts +1 -2
  28. package/lib/components/hotel-cancel/components/hotel-content/hotel-content.component.d.ts +1 -1
  29. package/lib/components/hotel-cancel/services/almatar-hotel-auto-cancellation-endpoints.service.d.ts +6 -0
  30. package/lib/components/reissue-dialog/reissue-dialog.component.d.ts +34 -0
  31. package/lib/components/reissue-dialog/reissue-upgrade.store.d.ts +96 -0
  32. package/lib/components/reissue-dialog/reissue.store.d.ts +124 -0
  33. package/lib/components/reissue-dialog/steps/reissue-date-step/reissue-date-step.component.d.ts +11 -0
  34. package/lib/components/reissue-dialog/steps/reissue-legs-step/reissue-legs-step.component.d.ts +16 -0
  35. package/lib/components/reissue-dialog/steps/reissue-notice/reissue-notice.component.d.ts +8 -0
  36. package/lib/components/reissue-dialog/steps/reissue-route-step/reissue-route-step.component.d.ts +21 -0
  37. package/lib/components/reissue-dialog/steps/reissue-service-step/reissue-service-step.component.d.ts +17 -0
  38. package/lib/components/reissue-dialog/steps/reissue-upgrade-confirm-step/reissue-upgrade-confirm-step.component.d.ts +28 -0
  39. package/lib/components/reissue-dialog/steps/reissue-upgrade-step/reissue-upgrade-step.component.d.ts +13 -0
  40. package/lib/components/result-panel/result-panel.component.d.ts +1 -1
  41. package/lib/components/workflow-actions/workflow-actions.component.d.ts +3 -1
  42. package/lib/config/dashboard.config.d.ts +29 -0
  43. package/lib/models/reissue.types.d.ts +147 -0
  44. package/lib/pages/smart-booking-dashboard/smart-booking-dashboard.component.d.ts +4 -1
  45. package/lib/services/reissue.service.d.ts +45 -0
  46. package/lib/services/toast.service.d.ts +14 -1
  47. package/lib/utils/reissue-format.d.ts +28 -0
  48. package/lib/utils/reissue-url.builder.d.ts +24 -0
  49. package/package.json +9 -7
  50. package/public-api.d.ts +5 -1
@@ -1,11 +1,11 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, makeEnvironmentProviders, inject, Injectable, input, model, output, Component, computed, signal, effect, EventEmitter, Inject, Optional, Input, Output, NgModule, ChangeDetectionStrategy, DestroyRef, Pipe, HostListener } from '@angular/core';
2
+ import { InjectionToken, makeEnvironmentProviders, inject, Injectable, input, model, output, Component, computed, signal, effect, EventEmitter, Inject, Optional, Input, Output, NgModule, ChangeDetectionStrategy, DestroyRef, Pipe, untracked, Injector, runInInjectionContext, HostListener } from '@angular/core';
3
3
  import * as i1$2 from '@angular/common';
4
4
  import { NgTemplateOutlet, NgClass, DOCUMENT, CommonModule, LowerCasePipe, UpperCasePipe, NgIf } from '@angular/common';
5
5
  import * as i2$1 from 'primeng/button';
6
6
  import { ButtonModule } from 'primeng/button';
7
7
  import { HttpClient, HttpRequest, HttpEventType } from '@angular/common/http';
8
- import { map, filter, catchError as catchError$1 } from 'rxjs/operators';
8
+ import { map, filter, catchError as catchError$1, switchMap, debounceTime, distinctUntilChanged, tap, finalize } from 'rxjs/operators';
9
9
  import { ToastrService } from 'ngx-toastr';
10
10
  import * as i1 from '@angular/forms';
11
11
  import { FormsModule } from '@angular/forms';
@@ -19,9 +19,8 @@ import * as i1$1 from 'primeng/dialog';
19
19
  import { DialogModule } from 'primeng/dialog';
20
20
  import * as i1$3 from 'primeng/skeleton';
21
21
  import { SkeletonModule } from 'primeng/skeleton';
22
- import * as i4 from 'primeng/sidebar';
23
- import { SidebarModule } from 'primeng/sidebar';
24
- import { of, BehaviorSubject, Subject, catchError, map as map$1, throwError } from 'rxjs';
22
+ import { of, BehaviorSubject, Subject, catchError, map as map$1, throwError, EMPTY, isObservable, firstValueFrom } from 'rxjs';
23
+ import * as i2$2 from 'primeng/api';
25
24
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
26
25
  import * as i1$4 from '@angular/router';
27
26
  import { ActivatedRoute, RouterModule } from '@angular/router';
@@ -32,11 +31,12 @@ import { TagModule } from 'primeng/tag';
32
31
  import * as i5$1 from '@ng-select/ng-select';
33
32
  import { NgSelectModule } from '@ng-select/ng-select';
34
33
  import { trigger, transition, style, animate } from '@angular/animations';
35
- import * as i2$2 from 'primeng/api';
36
34
 
37
35
  const SBD_DEFAULT_CONFIG = {
38
36
  apiBase: '/api',
39
37
  platform: 'BO',
38
+ webBaseUrl: '',
39
+ reissueEnabled: false,
40
40
  };
41
41
  const SBD_CONFIG = new InjectionToken('SBD_CONFIG', {
42
42
  providedIn: 'root',
@@ -113,14 +113,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
113
113
  args: [{ providedIn: 'root' }]
114
114
  }] });
115
115
 
116
+ const SBD_TOAST_HANDLER = new InjectionToken('SBD_TOAST_HANDLER');
116
117
  class SbdToastService {
117
118
  constructor() {
118
- this.toastr = inject(ToastrService);
119
+ // The host's handler wins; ngx-toastr is the fallback and is optional, so a host that never configured it still works.
120
+ this.handler = inject(SBD_TOAST_HANDLER, { optional: true }) ?? inject(ToastrService, { optional: true });
121
+ }
122
+ success(msg) { this.show('success', msg); }
123
+ error(msg) { this.show('error', msg); }
124
+ info(msg) { this.show('info', msg); }
125
+ warning(msg) { this.show('warning', msg); }
126
+ show(kind, msg) {
127
+ if (this.handler)
128
+ this.handler[kind](msg);
129
+ else
130
+ console.warn(`[smart-booking-dashboard] ${kind}: ${msg}`);
119
131
  }
120
- success(msg) { this.toastr.success(msg); }
121
- error(msg) { this.toastr.error(msg); }
122
- info(msg) { this.toastr.info(msg); }
123
- warning(msg) { this.toastr.warning(msg); }
124
132
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdToastService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
125
133
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdToastService, providedIn: 'root' }); }
126
134
  }
@@ -1216,14 +1224,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
1216
1224
  class SbdWorkflowActionsComponent {
1217
1225
  constructor() {
1218
1226
  this.bookingId = input.required();
1227
+ this.showReissue = input(false);
1219
1228
  this.cancelBooking = output();
1229
+ this.reissue = output();
1220
1230
  }
1221
1231
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdWorkflowActionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1222
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.2.14", type: SbdWorkflowActionsComponent, isStandalone: true, selector: "app-sbd-workflow-actions", inputs: { bookingId: { classPropertyName: "bookingId", publicName: "bookingId", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { cancelBooking: "cancelBooking" }, ngImport: i0, template: "<div class=\"sbd-workflow-actions\">\n <button pButton label=\"Cancel Booking\" icon=\"pi pi-times-circle\"\n class=\"p-button-danger p-button-outlined p-button-sm\"\n (click)=\"cancelBooking.emit()\">\n </button>\n\n <button pButton label=\"Send Ticket\" icon=\"pi pi-send\"\n class=\"p-button-outlined p-button-sm p-button-secondary\"\n [disabled]=\"true\"\n pTooltip=\"Coming soon\" tooltipPosition=\"bottom\">\n </button>\n\n <button pButton label=\"Flight Status\" icon=\"pi pi-chart-bar\"\n class=\"p-button-outlined p-button-sm p-button-secondary\"\n [disabled]=\"true\"\n pTooltip=\"Coming soon\" tooltipPosition=\"bottom\">\n </button>\n\n <button pButton label=\"Select Seat\" icon=\"pi pi-th-large\"\n class=\"p-button-outlined p-button-sm p-button-secondary\"\n [disabled]=\"true\"\n pTooltip=\"Coming soon\" tooltipPosition=\"bottom\">\n </button>\n\n <button pButton label=\"Add Baggage\" icon=\"pi pi-briefcase\"\n class=\"p-button-outlined p-button-sm p-button-secondary\"\n [disabled]=\"true\"\n pTooltip=\"Coming soon\" tooltipPosition=\"bottom\">\n </button>\n\n <button pButton label=\"Add Service\" icon=\"pi pi-plus-circle\"\n class=\"p-button-outlined p-button-sm p-button-secondary\"\n [disabled]=\"true\"\n pTooltip=\"Coming soon\" tooltipPosition=\"bottom\">\n </button>\n</div>\n", styles: [".sbd-workflow-actions{display:flex;gap:10px;align-items:center;flex-wrap:wrap}\n"], dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i2$1.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "label", "icon", "loading"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i5.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "fitContent", "pTooltip", "tooltipDisabled", "tooltipOptions"] }] }); }
1232
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SbdWorkflowActionsComponent, isStandalone: true, selector: "app-sbd-workflow-actions", inputs: { bookingId: { classPropertyName: "bookingId", publicName: "bookingId", isSignal: true, isRequired: true, transformFunction: null }, showReissue: { classPropertyName: "showReissue", publicName: "showReissue", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cancelBooking: "cancelBooking", reissue: "reissue" }, ngImport: i0, template: "<div class=\"sbd-workflow-actions\">\n <button pButton label=\"Cancel Booking\" icon=\"pi pi-times-circle\"\n class=\"p-button-danger p-button-outlined p-button-sm\"\n (click)=\"cancelBooking.emit()\">\n </button>\n\n @if (showReissue()) {\n <button pButton label=\"Reissue Booking\" icon=\"pi pi-refresh\"\n class=\"p-button-outlined p-button-sm\"\n (click)=\"reissue.emit()\">\n </button>\n }\n\n <button pButton label=\"Send Ticket\" icon=\"pi pi-send\"\n class=\"p-button-outlined p-button-sm p-button-secondary\"\n [disabled]=\"true\"\n pTooltip=\"Coming soon\" tooltipPosition=\"bottom\">\n </button>\n\n <button pButton label=\"Flight Status\" icon=\"pi pi-chart-bar\"\n class=\"p-button-outlined p-button-sm p-button-secondary\"\n [disabled]=\"true\"\n pTooltip=\"Coming soon\" tooltipPosition=\"bottom\">\n </button>\n\n <button pButton label=\"Select Seat\" icon=\"pi pi-th-large\"\n class=\"p-button-outlined p-button-sm p-button-secondary\"\n [disabled]=\"true\"\n pTooltip=\"Coming soon\" tooltipPosition=\"bottom\">\n </button>\n\n <button pButton label=\"Add Baggage\" icon=\"pi pi-briefcase\"\n class=\"p-button-outlined p-button-sm p-button-secondary\"\n [disabled]=\"true\"\n pTooltip=\"Coming soon\" tooltipPosition=\"bottom\">\n </button>\n\n <button pButton label=\"Add Service\" icon=\"pi pi-plus-circle\"\n class=\"p-button-outlined p-button-sm p-button-secondary\"\n [disabled]=\"true\"\n pTooltip=\"Coming soon\" tooltipPosition=\"bottom\">\n </button>\n</div>\n", styles: [".sbd-workflow-actions{display:flex;gap:10px;align-items:center;flex-wrap:wrap}\n"], dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i2$1.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "label", "icon", "loading"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i5.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "fitContent", "pTooltip", "tooltipDisabled", "tooltipOptions"] }] }); }
1223
1233
  }
1224
1234
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdWorkflowActionsComponent, decorators: [{
1225
1235
  type: Component,
1226
- args: [{ selector: 'app-sbd-workflow-actions', standalone: true, imports: [ButtonModule, TooltipModule], template: "<div class=\"sbd-workflow-actions\">\n <button pButton label=\"Cancel Booking\" icon=\"pi pi-times-circle\"\n class=\"p-button-danger p-button-outlined p-button-sm\"\n (click)=\"cancelBooking.emit()\">\n </button>\n\n <button pButton label=\"Send Ticket\" icon=\"pi pi-send\"\n class=\"p-button-outlined p-button-sm p-button-secondary\"\n [disabled]=\"true\"\n pTooltip=\"Coming soon\" tooltipPosition=\"bottom\">\n </button>\n\n <button pButton label=\"Flight Status\" icon=\"pi pi-chart-bar\"\n class=\"p-button-outlined p-button-sm p-button-secondary\"\n [disabled]=\"true\"\n pTooltip=\"Coming soon\" tooltipPosition=\"bottom\">\n </button>\n\n <button pButton label=\"Select Seat\" icon=\"pi pi-th-large\"\n class=\"p-button-outlined p-button-sm p-button-secondary\"\n [disabled]=\"true\"\n pTooltip=\"Coming soon\" tooltipPosition=\"bottom\">\n </button>\n\n <button pButton label=\"Add Baggage\" icon=\"pi pi-briefcase\"\n class=\"p-button-outlined p-button-sm p-button-secondary\"\n [disabled]=\"true\"\n pTooltip=\"Coming soon\" tooltipPosition=\"bottom\">\n </button>\n\n <button pButton label=\"Add Service\" icon=\"pi pi-plus-circle\"\n class=\"p-button-outlined p-button-sm p-button-secondary\"\n [disabled]=\"true\"\n pTooltip=\"Coming soon\" tooltipPosition=\"bottom\">\n </button>\n</div>\n", styles: [".sbd-workflow-actions{display:flex;gap:10px;align-items:center;flex-wrap:wrap}\n"] }]
1236
+ args: [{ selector: 'app-sbd-workflow-actions', standalone: true, imports: [ButtonModule, TooltipModule], template: "<div class=\"sbd-workflow-actions\">\n <button pButton label=\"Cancel Booking\" icon=\"pi pi-times-circle\"\n class=\"p-button-danger p-button-outlined p-button-sm\"\n (click)=\"cancelBooking.emit()\">\n </button>\n\n @if (showReissue()) {\n <button pButton label=\"Reissue Booking\" icon=\"pi pi-refresh\"\n class=\"p-button-outlined p-button-sm\"\n (click)=\"reissue.emit()\">\n </button>\n }\n\n <button pButton label=\"Send Ticket\" icon=\"pi pi-send\"\n class=\"p-button-outlined p-button-sm p-button-secondary\"\n [disabled]=\"true\"\n pTooltip=\"Coming soon\" tooltipPosition=\"bottom\">\n </button>\n\n <button pButton label=\"Flight Status\" icon=\"pi pi-chart-bar\"\n class=\"p-button-outlined p-button-sm p-button-secondary\"\n [disabled]=\"true\"\n pTooltip=\"Coming soon\" tooltipPosition=\"bottom\">\n </button>\n\n <button pButton label=\"Select Seat\" icon=\"pi pi-th-large\"\n class=\"p-button-outlined p-button-sm p-button-secondary\"\n [disabled]=\"true\"\n pTooltip=\"Coming soon\" tooltipPosition=\"bottom\">\n </button>\n\n <button pButton label=\"Add Baggage\" icon=\"pi pi-briefcase\"\n class=\"p-button-outlined p-button-sm p-button-secondary\"\n [disabled]=\"true\"\n pTooltip=\"Coming soon\" tooltipPosition=\"bottom\">\n </button>\n\n <button pButton label=\"Add Service\" icon=\"pi pi-plus-circle\"\n class=\"p-button-outlined p-button-sm p-button-secondary\"\n [disabled]=\"true\"\n pTooltip=\"Coming soon\" tooltipPosition=\"bottom\">\n </button>\n</div>\n", styles: [".sbd-workflow-actions{display:flex;gap:10px;align-items:center;flex-wrap:wrap}\n"] }]
1227
1237
  }] });
1228
1238
 
1229
1239
  class SbdActionCardComponent {
@@ -2880,11 +2890,11 @@ class AsideComponent {
2880
2890
  this.document.body.style.overflow = '';
2881
2891
  }
2882
2892
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AsideComponent, deps: [{ token: SharedService }, { token: DOCUMENT }, { token: AsideBackButtonService, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
2883
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AsideComponent, isStandalone: true, selector: "almatar-aside", inputs: { asideTitle: "asideTitle", showHeader: "showHeader", headerTemplate: "headerTemplate", whiteHeader: "whiteHeader", showAside: "showAside", width: "width", modal: "modal", closable: "closable", transitionOptions: "transitionOptions", closeIconTemplate: "closeIconTemplate", interceptClose: "interceptClose" }, outputs: { onClose: "onClose", closeRequested: "closeRequested" }, ngImport: i0, template: "<!-- `position=\"right\"` is intentional for both directions: PrimeNG's flex\n positioning is writing-direction aware, so under dir=\"rtl\" (Arabic) it\n mirrors to the visual left on its own. Passing \"left\" for Arabic\n double-flips it back to the right. -->\n<p-sidebar\n class=\"almatar_aside\"\n [ngClass]=\"{ 'almatar_aside--white-header': isWhiteHeader }\"\n (onShow)=\"onDialogShow()\"\n [showCloseIcon]=\"closable && !isInAppView\"\n (onHide)=\"onCloseModal()\"\n [visible]=\"showAside\"\n (visibleChange)=\"onVisibleChange($event)\"\n position=\"right\"\n [style]=\"{ width: width || (isWhiteHeader ? '580px' : '490px') }\"\n [transitionOptions]=\"transitionOptions\"\n [modal]=\"modal\"\n>\n @if (showHeader) {\n @if (showBackButton) {\n <!-- Rendered into the header's leading content, alongside (not instead\n of) PrimeNG's own close (X) button \u2014 so both are visible together,\n and the X keeps its normal close behavior untouched. -->\n <ng-template pTemplate=\"header\">\n <!-- One flex group, not two separate header children \u2014 otherwise\n `.p-sidebar-header`'s `justify-content: space-between` spreads the\n arrow and title apart instead of keeping them side by side. -->\n <div class=\"almatar-aside__back-header\">\n <i\n class=\"almatar-aside__back-icon\"\n [class]=\"backIconClass\"\n (click)=\"onBackIconClick($event)\"\n ></i>\n <span class=\"p-sidebar-header-content\">{{ asideTitle }}</span>\n </div>\n </ng-template>\n } @else if (headerTemplate) {\n <ng-template pTemplate=\"header\">\n <ng-container *ngTemplateOutlet=\"headerTemplate\"></ng-container>\n </ng-template>\n } @else {\n <ng-template pTemplate=\"header\">\n <span class=\"p-sidebar-header-content\">{{ asideTitle }}</span>\n </ng-template>\n }\n }\n\n @if (closeIconTemplate) {\n <!-- Custom close icon template -->\n <ng-template pTemplate=\"closeicon\">\n <ng-container *ngTemplateOutlet=\"closeIconTemplate\"></ng-container>\n </ng-template>\n } @else if (isWhiteHeader) {\n <ng-template pTemplate=\"closeicon\">\n <img src=\"/assets/icons/close-dark.svg\" alt=\"\" width=\"24\" height=\"24\" />\n </ng-template>\n }\n\n <ng-content></ng-content>\n</p-sidebar>\n", styles: [".almatar_aside ::ng-deep .p-sidebar{margin:0!important;height:100svh;max-height:100svh!important;border-radius:0;border:none;transform:unset}@media (max-width: 768px){.almatar_aside ::ng-deep .p-sidebar{width:100vw!important;margin:auto!important}}.almatar_aside ::ng-deep .p-sidebar-content{height:100vh}.almatar_aside ::ng-deep .p-sidebar-mask{background-color:#0000008a}.almatar_aside ::ng-deep .p-sidebar-header{background:var(--almatar-secondary-surface);border:none}.almatar_aside ::ng-deep .p-sidebar-header .p-sidebar-header-content,.almatar_aside ::ng-deep .p-sidebar-header .p-sidebar-close-icon{color:var(--almatar-neutral-icon-secondary-static)}.almatar_aside ::ng-deep .p-sidebar-header .p-sidebar-header-content{color:var(--almatar-secondary-text-on-surface);font-size:calc(21 * var(--almatar-font-size-ratio, 1) * 1px);margin:0;font-weight:var(--almatar-font-weight-regular)}.almatar_aside ::ng-deep .p-sidebar-header .almatar-aside__back-header{display:flex;align-items:center;gap:12px}.almatar_aside ::ng-deep .p-sidebar-header .almatar-aside__back-icon{cursor:pointer;font-size:1.25rem;color:var(--almatar-neutral-icon-secondary-static)}.almatar_aside ::ng-deep .p-sidebar-close-icon{font-size:1.5rem!important}.almatar_aside ::ng-deep .p-sidebar-content{background-color:var(--almatar-neutral-surface-card);padding:0}.almatar_aside--white-header ::ng-deep .p-sidebar-header{background:var(--almatar-neutral-surface-card);border-bottom:1px solid var(--almatar-primary-border-disabled)}.almatar_aside--white-header ::ng-deep .p-sidebar-header .p-sidebar-close-icon{color:var(--almatar-neutral-icon-primary-static)}.almatar_aside--white-header ::ng-deep .p-sidebar-header .p-sidebar-header-content{color:var(--almatar-neutral-text-primary-static);font-weight:var(--almatar-font-weight-regular)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: SidebarModule }, { kind: "component", type: i4.Sidebar, selector: "p-sidebar", inputs: ["appendTo", "blockScroll", "style", "styleClass", "ariaCloseLabel", "autoZIndex", "baseZIndex", "modal", "dismissible", "showCloseIcon", "closeOnEscape", "transitionOptions", "visible", "position", "fullScreen"], outputs: ["onShow", "onHide", "visibleChange"] }] }); }
2893
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AsideComponent, isStandalone: true, selector: "almatar-aside", inputs: { asideTitle: "asideTitle", showHeader: "showHeader", headerTemplate: "headerTemplate", whiteHeader: "whiteHeader", showAside: "showAside", width: "width", modal: "modal", closable: "closable", transitionOptions: "transitionOptions", interceptClose: "interceptClose" }, outputs: { onClose: "onClose", closeRequested: "closeRequested" }, ngImport: i0, template: "<!-- A side panel built on `p-dialog` with `position=\"right\"`, not on PrimeNG's sidebar/drawer: `p-dialog` exists with\n the same name in PrimeNG 14 (the backoffice) and 20 (Helix), while `primeng/sidebar` was removed in 20 and\n `primeng/drawer` does not exist in 14.\n `position=\"right\"` is intentional for both directions: the flex positioning is writing-direction aware, so\n under dir=\"rtl\" (Arabic) it mirrors to the visual left on its own. Passing \"left\" for Arabic double-flips it\n back to the right. -->\n<p-dialog\n class=\"almatar_aside\"\n [ngClass]=\"{ 'almatar_aside--white-header': isWhiteHeader }\"\n position=\"right\"\n [visible]=\"showAside\"\n (visibleChange)=\"onVisibleChange($event)\"\n (onShow)=\"onDialogShow()\"\n (onHide)=\"onCloseModal()\"\n [modal]=\"modal\"\n [closable]=\"closable && !isInAppView\"\n [showHeader]=\"showHeader\"\n [draggable]=\"false\"\n [resizable]=\"false\"\n [style]=\"{ width: width || (isWhiteHeader ? '580px' : '490px') }\"\n [transitionOptions]=\"transitionOptions\"\n>\n @if (showHeader) {\n @if (showBackButton) {\n <!-- Rendered into the header's leading content, alongside (not instead of) the dialog's own close (X)\n button, so both are visible together and the X keeps its normal close behavior untouched. -->\n <ng-template pTemplate=\"header\">\n <!-- One flex group, not two separate header children: the header's `justify-content: space-between`\n would otherwise spread the arrow and the title apart. -->\n <div class=\"almatar-aside__back-header\">\n <i\n class=\"almatar-aside__back-icon\"\n [class]=\"backIconClass\"\n (click)=\"onBackIconClick($event)\"\n ></i>\n <span class=\"almatar-aside__title\">{{ asideTitle }}</span>\n </div>\n </ng-template>\n } @else if (headerTemplate) {\n <ng-template pTemplate=\"header\">\n <ng-container *ngTemplateOutlet=\"headerTemplate\"></ng-container>\n </ng-template>\n } @else {\n <ng-template pTemplate=\"header\">\n <span class=\"almatar-aside__title\">{{ asideTitle }}</span>\n </ng-template>\n }\n }\n\n <ng-content></ng-content>\n</p-dialog>\n", styles: [".almatar_aside ::ng-deep .p-dialog{margin:0!important;height:100svh;max-height:100svh!important;border-radius:0;border:none;transform:unset}@media (max-width: 768px){.almatar_aside ::ng-deep .p-dialog{width:100vw!important}}.almatar_aside ::ng-deep .p-dialog-mask.p-component-overlay,.almatar_aside ::ng-deep .p-dialog-mask.p-overlay-mask{background-color:#0000008a}.almatar_aside ::ng-deep .p-dialog-header{background:var(--almatar-secondary-surface);border:none;border-radius:0;padding:1rem 1.25rem}.almatar_aside ::ng-deep .p-dialog-header .almatar-aside__title,.almatar_aside ::ng-deep .p-dialog-header .p-dialog-header-close-icon,.almatar_aside ::ng-deep .p-dialog-header .p-dialog-close-icon{color:var(--almatar-neutral-icon-secondary-static)}.almatar_aside ::ng-deep .p-dialog-header .almatar-aside__title{color:var(--almatar-secondary-text-on-surface);font-size:calc(21 * var(--almatar-font-size-ratio, 1) * 1px);margin:0;font-weight:var(--almatar-font-weight-regular)}.almatar_aside ::ng-deep .p-dialog-header .almatar-aside__back-header{display:flex;align-items:center;gap:12px}.almatar_aside ::ng-deep .p-dialog-header .almatar-aside__back-icon{cursor:pointer;font-size:1.25rem;color:var(--almatar-neutral-icon-secondary-static)}.almatar_aside ::ng-deep .p-dialog-header-close-icon,.almatar_aside ::ng-deep .p-dialog-close-icon{font-size:1.5rem!important}.almatar_aside ::ng-deep .p-dialog-content{height:100%;flex-grow:1;background-color:var(--almatar-neutral-surface-card);padding:0}.almatar_aside--white-header ::ng-deep .p-dialog-header{background:var(--almatar-neutral-surface-card);border-bottom:1px solid var(--almatar-primary-border-disabled)}.almatar_aside--white-header ::ng-deep .p-dialog-header .p-dialog-header-close-icon,.almatar_aside--white-header ::ng-deep .p-dialog-header .p-dialog-close-icon{color:var(--almatar-neutral-icon-primary-static)}.almatar_aside--white-header ::ng-deep .p-dialog-header .almatar-aside__title{color:var(--almatar-neutral-text-primary-static);font-weight:var(--almatar-font-weight-regular)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i1$1.Dialog, selector: "p-dialog", inputs: ["header", "draggable", "resizable", "positionLeft", "positionTop", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "responsive", "appendTo", "breakpoints", "styleClass", "maskStyleClass", "showHeader", "breakpoint", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "visible", "style", "position"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "directive", type: i2$2.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }] }); }
2884
2894
  }
2885
2895
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AsideComponent, decorators: [{
2886
2896
  type: Component,
2887
- args: [{ selector: 'almatar-aside', standalone: true, imports: [CommonModule, SidebarModule], template: "<!-- `position=\"right\"` is intentional for both directions: PrimeNG's flex\n positioning is writing-direction aware, so under dir=\"rtl\" (Arabic) it\n mirrors to the visual left on its own. Passing \"left\" for Arabic\n double-flips it back to the right. -->\n<p-sidebar\n class=\"almatar_aside\"\n [ngClass]=\"{ 'almatar_aside--white-header': isWhiteHeader }\"\n (onShow)=\"onDialogShow()\"\n [showCloseIcon]=\"closable && !isInAppView\"\n (onHide)=\"onCloseModal()\"\n [visible]=\"showAside\"\n (visibleChange)=\"onVisibleChange($event)\"\n position=\"right\"\n [style]=\"{ width: width || (isWhiteHeader ? '580px' : '490px') }\"\n [transitionOptions]=\"transitionOptions\"\n [modal]=\"modal\"\n>\n @if (showHeader) {\n @if (showBackButton) {\n <!-- Rendered into the header's leading content, alongside (not instead\n of) PrimeNG's own close (X) button \u2014 so both are visible together,\n and the X keeps its normal close behavior untouched. -->\n <ng-template pTemplate=\"header\">\n <!-- One flex group, not two separate header children \u2014 otherwise\n `.p-sidebar-header`'s `justify-content: space-between` spreads the\n arrow and title apart instead of keeping them side by side. -->\n <div class=\"almatar-aside__back-header\">\n <i\n class=\"almatar-aside__back-icon\"\n [class]=\"backIconClass\"\n (click)=\"onBackIconClick($event)\"\n ></i>\n <span class=\"p-sidebar-header-content\">{{ asideTitle }}</span>\n </div>\n </ng-template>\n } @else if (headerTemplate) {\n <ng-template pTemplate=\"header\">\n <ng-container *ngTemplateOutlet=\"headerTemplate\"></ng-container>\n </ng-template>\n } @else {\n <ng-template pTemplate=\"header\">\n <span class=\"p-sidebar-header-content\">{{ asideTitle }}</span>\n </ng-template>\n }\n }\n\n @if (closeIconTemplate) {\n <!-- Custom close icon template -->\n <ng-template pTemplate=\"closeicon\">\n <ng-container *ngTemplateOutlet=\"closeIconTemplate\"></ng-container>\n </ng-template>\n } @else if (isWhiteHeader) {\n <ng-template pTemplate=\"closeicon\">\n <img src=\"/assets/icons/close-dark.svg\" alt=\"\" width=\"24\" height=\"24\" />\n </ng-template>\n }\n\n <ng-content></ng-content>\n</p-sidebar>\n", styles: [".almatar_aside ::ng-deep .p-sidebar{margin:0!important;height:100svh;max-height:100svh!important;border-radius:0;border:none;transform:unset}@media (max-width: 768px){.almatar_aside ::ng-deep .p-sidebar{width:100vw!important;margin:auto!important}}.almatar_aside ::ng-deep .p-sidebar-content{height:100vh}.almatar_aside ::ng-deep .p-sidebar-mask{background-color:#0000008a}.almatar_aside ::ng-deep .p-sidebar-header{background:var(--almatar-secondary-surface);border:none}.almatar_aside ::ng-deep .p-sidebar-header .p-sidebar-header-content,.almatar_aside ::ng-deep .p-sidebar-header .p-sidebar-close-icon{color:var(--almatar-neutral-icon-secondary-static)}.almatar_aside ::ng-deep .p-sidebar-header .p-sidebar-header-content{color:var(--almatar-secondary-text-on-surface);font-size:calc(21 * var(--almatar-font-size-ratio, 1) * 1px);margin:0;font-weight:var(--almatar-font-weight-regular)}.almatar_aside ::ng-deep .p-sidebar-header .almatar-aside__back-header{display:flex;align-items:center;gap:12px}.almatar_aside ::ng-deep .p-sidebar-header .almatar-aside__back-icon{cursor:pointer;font-size:1.25rem;color:var(--almatar-neutral-icon-secondary-static)}.almatar_aside ::ng-deep .p-sidebar-close-icon{font-size:1.5rem!important}.almatar_aside ::ng-deep .p-sidebar-content{background-color:var(--almatar-neutral-surface-card);padding:0}.almatar_aside--white-header ::ng-deep .p-sidebar-header{background:var(--almatar-neutral-surface-card);border-bottom:1px solid var(--almatar-primary-border-disabled)}.almatar_aside--white-header ::ng-deep .p-sidebar-header .p-sidebar-close-icon{color:var(--almatar-neutral-icon-primary-static)}.almatar_aside--white-header ::ng-deep .p-sidebar-header .p-sidebar-header-content{color:var(--almatar-neutral-text-primary-static);font-weight:var(--almatar-font-weight-regular)}\n"] }]
2897
+ args: [{ selector: 'almatar-aside', standalone: true, imports: [CommonModule, DialogModule], template: "<!-- A side panel built on `p-dialog` with `position=\"right\"`, not on PrimeNG's sidebar/drawer: `p-dialog` exists with\n the same name in PrimeNG 14 (the backoffice) and 20 (Helix), while `primeng/sidebar` was removed in 20 and\n `primeng/drawer` does not exist in 14.\n `position=\"right\"` is intentional for both directions: the flex positioning is writing-direction aware, so\n under dir=\"rtl\" (Arabic) it mirrors to the visual left on its own. Passing \"left\" for Arabic double-flips it\n back to the right. -->\n<p-dialog\n class=\"almatar_aside\"\n [ngClass]=\"{ 'almatar_aside--white-header': isWhiteHeader }\"\n position=\"right\"\n [visible]=\"showAside\"\n (visibleChange)=\"onVisibleChange($event)\"\n (onShow)=\"onDialogShow()\"\n (onHide)=\"onCloseModal()\"\n [modal]=\"modal\"\n [closable]=\"closable && !isInAppView\"\n [showHeader]=\"showHeader\"\n [draggable]=\"false\"\n [resizable]=\"false\"\n [style]=\"{ width: width || (isWhiteHeader ? '580px' : '490px') }\"\n [transitionOptions]=\"transitionOptions\"\n>\n @if (showHeader) {\n @if (showBackButton) {\n <!-- Rendered into the header's leading content, alongside (not instead of) the dialog's own close (X)\n button, so both are visible together and the X keeps its normal close behavior untouched. -->\n <ng-template pTemplate=\"header\">\n <!-- One flex group, not two separate header children: the header's `justify-content: space-between`\n would otherwise spread the arrow and the title apart. -->\n <div class=\"almatar-aside__back-header\">\n <i\n class=\"almatar-aside__back-icon\"\n [class]=\"backIconClass\"\n (click)=\"onBackIconClick($event)\"\n ></i>\n <span class=\"almatar-aside__title\">{{ asideTitle }}</span>\n </div>\n </ng-template>\n } @else if (headerTemplate) {\n <ng-template pTemplate=\"header\">\n <ng-container *ngTemplateOutlet=\"headerTemplate\"></ng-container>\n </ng-template>\n } @else {\n <ng-template pTemplate=\"header\">\n <span class=\"almatar-aside__title\">{{ asideTitle }}</span>\n </ng-template>\n }\n }\n\n <ng-content></ng-content>\n</p-dialog>\n", styles: [".almatar_aside ::ng-deep .p-dialog{margin:0!important;height:100svh;max-height:100svh!important;border-radius:0;border:none;transform:unset}@media (max-width: 768px){.almatar_aside ::ng-deep .p-dialog{width:100vw!important}}.almatar_aside ::ng-deep .p-dialog-mask.p-component-overlay,.almatar_aside ::ng-deep .p-dialog-mask.p-overlay-mask{background-color:#0000008a}.almatar_aside ::ng-deep .p-dialog-header{background:var(--almatar-secondary-surface);border:none;border-radius:0;padding:1rem 1.25rem}.almatar_aside ::ng-deep .p-dialog-header .almatar-aside__title,.almatar_aside ::ng-deep .p-dialog-header .p-dialog-header-close-icon,.almatar_aside ::ng-deep .p-dialog-header .p-dialog-close-icon{color:var(--almatar-neutral-icon-secondary-static)}.almatar_aside ::ng-deep .p-dialog-header .almatar-aside__title{color:var(--almatar-secondary-text-on-surface);font-size:calc(21 * var(--almatar-font-size-ratio, 1) * 1px);margin:0;font-weight:var(--almatar-font-weight-regular)}.almatar_aside ::ng-deep .p-dialog-header .almatar-aside__back-header{display:flex;align-items:center;gap:12px}.almatar_aside ::ng-deep .p-dialog-header .almatar-aside__back-icon{cursor:pointer;font-size:1.25rem;color:var(--almatar-neutral-icon-secondary-static)}.almatar_aside ::ng-deep .p-dialog-header-close-icon,.almatar_aside ::ng-deep .p-dialog-close-icon{font-size:1.5rem!important}.almatar_aside ::ng-deep .p-dialog-content{height:100%;flex-grow:1;background-color:var(--almatar-neutral-surface-card);padding:0}.almatar_aside--white-header ::ng-deep .p-dialog-header{background:var(--almatar-neutral-surface-card);border-bottom:1px solid var(--almatar-primary-border-disabled)}.almatar_aside--white-header ::ng-deep .p-dialog-header .p-dialog-header-close-icon,.almatar_aside--white-header ::ng-deep .p-dialog-header .p-dialog-close-icon{color:var(--almatar-neutral-icon-primary-static)}.almatar_aside--white-header ::ng-deep .p-dialog-header .almatar-aside__title{color:var(--almatar-neutral-text-primary-static);font-weight:var(--almatar-font-weight-regular)}\n"] }]
2888
2898
  }], ctorParameters: () => [{ type: SharedService }, { type: Document, decorators: [{
2889
2899
  type: Inject,
2890
2900
  args: [DOCUMENT]
@@ -2908,8 +2918,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2908
2918
  type: Input
2909
2919
  }], transitionOptions: [{
2910
2920
  type: Input
2911
- }], closeIconTemplate: [{
2912
- type: Input
2913
2921
  }], interceptClose: [{
2914
2922
  type: Input
2915
2923
  }], onClose: [{
@@ -3157,6 +3165,7 @@ function resolveMyTripsViewedEntrySourceFromQueryParams(params) {
3157
3165
  class AlmatarHotelAutoCancellationEndpointsService {
3158
3166
  constructor() {
3159
3167
  this.api = inject(ApiService);
3168
+ this.apiBase = inject(SBD_CONFIG).apiBase;
3160
3169
  this.route = inject(ActivatedRoute);
3161
3170
  this.hotelManageBookingService = inject(HotelManageBookingService);
3162
3171
  this._almatarBookingId = signal('');
@@ -3179,8 +3188,15 @@ class AlmatarHotelAutoCancellationEndpointsService {
3179
3188
  }
3180
3189
  });
3181
3190
  }
3191
+ /**
3192
+ * Full URL of an endpoint from the enum. The enum paths start with `/api/`, which is the default `apiBase`; a host
3193
+ * that serves the API somewhere else (Helix: its own API origin) sets `apiBase`, so that part is swapped for it.
3194
+ */
3195
+ resolveEndpoint(path) {
3196
+ return `${this.apiBase.replace(/\/+$/, '')}/${path.replace(/^\/?api\//, '')}`;
3197
+ }
3182
3198
  getRefundDetails(packageIds = []) {
3183
- const endpoint = AlmatarHotelAutoCancellationEndpointsEnum.getRefundDetails;
3199
+ const endpoint = this.resolveEndpoint(AlmatarHotelAutoCancellationEndpointsEnum.getRefundDetails);
3184
3200
  const payload = {
3185
3201
  bookingId: `${this.hotelManageBookingService.selectedHotelBooking.value?.bookingId ||
3186
3202
  ''}`,
@@ -3472,7 +3488,7 @@ class AlmatarHotelAutoCancellationEndpointsService {
3472
3488
  return allRooms;
3473
3489
  }
3474
3490
  processAutoCancellation(packageIds, refundOption = 'originalPaymentMethod') {
3475
- const endpoint = AlmatarHotelAutoCancellationEndpointsEnum.processAutoCancellation;
3491
+ const endpoint = this.resolveEndpoint(AlmatarHotelAutoCancellationEndpointsEnum.processAutoCancellation);
3476
3492
  const uniquePackageIds = [...new Set(packageIds)]; // Ensure unique package IDs
3477
3493
  const payload = {
3478
3494
  bookingId: `${this.hotelManageBookingService.selectedHotelBooking.value?.bookingId ||
@@ -3522,7 +3538,7 @@ class AlmatarHotelAutoCancellationEndpointsService {
3522
3538
  }), catchError((error) => this.handleError(error, 'Failed to process cancellation')));
3523
3539
  }
3524
3540
  processNonRefundableCancellation(packageId, attachmentUrl, language = 'en') {
3525
- const endpoint = AlmatarHotelAutoCancellationEndpointsEnum.processAutoCancellation;
3541
+ const endpoint = this.resolveEndpoint(AlmatarHotelAutoCancellationEndpointsEnum.processAutoCancellation);
3526
3542
  const payload = {
3527
3543
  bookingId: `${this.hotelManageBookingService.selectedHotelBooking.value?.bookingId ||
3528
3544
  ''}`,
@@ -3798,7 +3814,7 @@ class AlmatarHotelAutoCancellationNonRefundableRoomComponent {
3798
3814
  this.uploadedFileUrl.set(null);
3799
3815
  const formData = new FormData();
3800
3816
  formData.append('file', file);
3801
- const req = new HttpRequest('POST', AlmatarHotelAutoCancellationEndpointsEnum.uploadAttachment, formData, {
3817
+ const req = new HttpRequest('POST', this.endpointsService.resolveEndpoint(AlmatarHotelAutoCancellationEndpointsEnum.uploadAttachment), formData, {
3802
3818
  reportProgress: true,
3803
3819
  responseType: 'json',
3804
3820
  });
@@ -5482,7 +5498,7 @@ class AlmatarHotelAutoCancellationComponent {
5482
5498
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AlmatarHotelAutoCancellationComponent, isStandalone: true, selector: "almatar-hotel-auto-cancellation", inputs: { selectedHotelBooking: { classPropertyName: "selectedHotelBooking", publicName: "selectedHotelBooking", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", cancellationSuccess: "cancellationSuccess", nonRefundableSubmitSuccess: "nonRefundableSubmitSuccess", chatWithAgent: "chatWithAgent" }, providers: [
5483
5499
  AlmatarHotelAutoCancellationService,
5484
5500
  AlmatarHotelAutoCancellationEndpointsService
5485
- ], ngImport: i0, template: "@if (showAside()) {\n <almatar-aside\n\n class=\"auto_cancellation_wrapper\"\n [asideTitle]=\"asideTitle()\"\n [showAside]=\"showAside()\"\n [whiteHeader]=\"true\"\n [showHeader]=\"false\"\n (onClose)=\"onCloseModal()\"\n >\n <!-- Custom Header with 60px height -->\n <div class=\"auto_cancellation__header\">\n @if (canGoBack()) {\n <button class=\"auto_cancellation__header-back\" (click)=\"onGoBack()\" type=\"button\">\n <i class=\"pi pi-arrow-left\"></i>\n </button>\n }\n <div class=\"auto_cancellation__header-content\">\n @if (currentStep() === stepEnum.selectRooms) {\n <h2 class=\"auto_cancellation__header-title\">\n @if (isSinglePackage()) {\n Cancel Rooms\n } @else {\n Select Rooms\n }\n </h2>\n <p class=\"auto_cancellation__header-subtitle\">\n @if (isSinglePackage()) {\n Review and confirm your cancellation\n } @else {\n Choose which room(s) you'd like to cancel\n }\n </p>\n } @else if (currentStep() === stepEnum.refundSummary) {\n <h2 class=\"auto_cancellation__header-title\">Cancel Booking Request</h2>\n <p class=\"auto_cancellation__header-subtitle\">Review your refund summary and proceed</p>\n } @else if (currentStep() === stepEnum.refundMethod) {\n <h2 class=\"auto_cancellation__header-title\">Select Refund Method</h2>\n <p class=\"auto_cancellation__header-subtitle\">Choose how you'd like to receive your refund</p>\n } @else {\n <h2 class=\"auto_cancellation__header-title\">{{ asideTitle() }}</h2>\n <p class=\"auto_cancellation__header-subtitle\">Complete your booking cancellation</p>\n }\n </div>\n <button class=\"auto_cancellation__header-close\" (click)=\"onCloseModal()\" type=\"button\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n\n <div class=\"auto_cancellation__component\" [class.isNusk]=\"isNusk()\">\n @if (isLoading()) {\n <div class=\"auto_cancellation__loading\">\n <app-loader></app-loader>\n </div>\n }\n\n @if (currentStep() === stepEnum.selectRooms && !isLoading()) {\n <almatar-hotel-auto-cancellation-select-rooms\n [packages]=\"packages()\"\n [hotelInfo]=\"hotelInfo()\"\n [bookingId]=\"selectedHotelBooking()?.bookingId\"\n [hotelId]=\"selectedHotelBooking()?.hotelId\"\n [currencyCode]=\"selectedHotelBooking()?.currencyCode || 'SAR'\"\n (continueToCancel)=\"onContinueToCancel($event)\"\n (closeModal)=\"onCloseModal()\"\n (nonRefundableSubmitSuccess)=\"onNonRefundableSubmitSuccess($event)\"\n (chatWithAgent)=\"onChatWithAgent()\"\n />\n }\n\n @if (currentStep() === stepEnum.refundSummary && !isLoading()) {\n <almatar-hotel-auto-cancellation-refund-summary\n [selectedRooms]=\"autoCancellationService.selectedRooms()\"\n [bookingId]=\"selectedHotelBooking()?.bookingId\"\n [hotelId]=\"selectedHotelBooking()?.id || selectedHotelBooking()?.hpk\"\n [hotelInfo]=\"hotelInfo()\"\n [selectionSkipped]=\"packages().length <= 1\"\n (keepRoom)=\"onKeepRoom()\"\n (selectRefundOption)=\"onSelectRefundOption($event)\"\n (closeModal)=\"onCloseModal()\"\n />\n }\n\n @if (currentStep() === stepEnum.refundMethod && !isLoading()) {\n <almatar-hotel-auto-cancellation-refund-method\n [refundDetailsData]=\"refundDetailsData()\"\n [currencyObj]=\"currentCurrency()\"\n [refundMethod]=\"refundMethod()\"\n [isCancelling]=\"isCancelling()\"\n (methodSelected)=\"onMethodSelected($event)\"\n (backRequested)=\"onBackFromRefundMethod()\"\n (confirmRequested)=\"confirmCancellation()\"\n (closeModal)=\"onCloseModal()\"\n [bookingId]=\"selectedHotelBooking()?.bookingId\"\n />\n }\n </div>\n </almatar-aside>\n}\n\n@if (showErrorModal()) {\n <div\n almatar-manage-booking-failed-modal\n [errorMessage]=\"hotelManageError()\"\n (cancelEvent)=\"closeAllModals()\"\n (confirmEvent)=\"closeAllModals()\"\n ></div>\n}\n\n@if (showResultOverlay() && resultOverlayData()) {\n <app-result-overlay\n [data]=\"resultOverlayData()!\"\n [show]=\"showResultOverlay()\"\n (confirm)=\"onResultOverlayConfirm()\"\n (cancel)=\"onResultOverlayCancel()\"\n />\n}\n", styles: [".auto_cancellation__header{height:60px;display:flex;align-items:center;justify-content:space-between;padding:0 20px;background:#fff;border-bottom:1px solid #e2e8f0;flex-shrink:0;gap:16px}.auto_cancellation__header-content{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0}.auto_cancellation__header-title{font-size:calc(18 * var(--almatar-font-size-ratio, 1) * 1px);font-weight:700;color:#1e293b;margin:0;line-height:1.3}.auto_cancellation__header-subtitle{font-size:calc(12 * var(--almatar-font-size-ratio, 1) * 1px);font-weight:400;color:#64748b;margin:0;line-height:1.4}.auto_cancellation__header-back{width:32px;height:32px;display:flex;align-items:center;justify-content:center;background:transparent;border:none;border-radius:6px;cursor:pointer;flex-shrink:0;transition:all .2s ease;margin-right:8px}.auto_cancellation__header-back i{font-size:18px;color:#64748b}.auto_cancellation__header-back:hover{background:#f1f5f9}.auto_cancellation__header-back:hover i{color:#1e293b}.auto_cancellation__header-back:active{background:#e2e8f0}.auto_cancellation__header-close{width:32px;height:32px;display:flex;align-items:center;justify-content:center;background:transparent;border:none;border-radius:6px;cursor:pointer;flex-shrink:0;transition:all .2s ease}.auto_cancellation__header-close i{font-size:18px;color:#64748b}.auto_cancellation__header-close:hover{background:#f1f5f9}.auto_cancellation__header-close:hover i{color:#1e293b}.auto_cancellation__header-close:active{background:#e2e8f0}.auto_cancellation__component{height:calc(100% - 60px)}.auto_cancellation__loading{display:flex;align-items:center;justify-content:center;height:100%;min-height:200px}.auto_cancellation_wrapper ::ng-deep almatar-hotel-auto-cancellation{display:block;height:100%}.auto_cancellation_wrapper ::ng-deep .p-sidebar-header{display:none!important}.auto_cancellation_wrapper ::ng-deep .p-sidebar-content{padding-top:0!important}.auto_cancellation_wrapper ::ng-deep .p-sidebar-close{display:none!important}.auto_cancellation_wrapper ::ng-deep .almatar_aside .p-dialog-content{background:var(--almatar-neutral-surface-screen-bg)}.auto_cancellation_wrapper ::ng-deep .main_btn,.auto_cancellation_wrapper ::ng-deep .btn{min-height:48px;padding:12px 24px;border-radius:10px;font-size:15px;font-weight:600;border:none;cursor:pointer;transition:all .2s ease;display:inline-flex;align-items:center;justify-content:center;gap:8px;position:relative;overflow:hidden}.auto_cancellation_wrapper ::ng-deep .main_btn:disabled,.auto_cancellation_wrapper ::ng-deep .btn:disabled{opacity:.5;cursor:not-allowed}.auto_cancellation_wrapper ::ng-deep .main_btn,.auto_cancellation_wrapper ::ng-deep .btn--primary{background:linear-gradient(135deg,#3b82f6,#2563eb);color:#fff;box-shadow:0 2px 8px #3b82f64d}.auto_cancellation_wrapper ::ng-deep .main_btn:hover:not(:disabled),.auto_cancellation_wrapper ::ng-deep .btn--primary:hover:not(:disabled){background:linear-gradient(135deg,#2563eb,#1d4ed8);box-shadow:0 4px 12px #3b82f666;transform:translateY(-1px)}.auto_cancellation_wrapper ::ng-deep .main_btn:active:not(:disabled),.auto_cancellation_wrapper ::ng-deep .btn--primary:active:not(:disabled){transform:translateY(0);box-shadow:0 2px 6px #3b82f64d}.auto_cancellation_wrapper ::ng-deep .btn--secondary{background:#f1f5f9;color:#1e293b;border:1px solid #e2e8f0}.auto_cancellation_wrapper ::ng-deep .btn--secondary:hover:not(:disabled){background:#e2e8f0;border-color:#cbd5e1;transform:translateY(-1px)}.auto_cancellation_wrapper ::ng-deep .btn--secondary:active:not(:disabled){background:#cbd5e1;transform:translateY(0)}.auto_cancellation_wrapper ::ng-deep .btn--danger{background:linear-gradient(135deg,#ef4444,#dc2626);color:#fff;box-shadow:0 2px 8px #ef44444d}.auto_cancellation_wrapper ::ng-deep .btn--danger:hover:not(:disabled){background:linear-gradient(135deg,#dc2626,#b91c1c);box-shadow:0 4px 12px #ef444466;transform:translateY(-1px)}.auto_cancellation_wrapper ::ng-deep .btn--danger:active:not(:disabled){transform:translateY(0);box-shadow:0 2px 6px #ef44444d}.auto_cancellation_wrapper ::ng-deep .main_btn--chat-with-us{background:linear-gradient(135deg,#10b981,#059669);box-shadow:0 2px 8px #10b9814d}.auto_cancellation_wrapper ::ng-deep .main_btn--chat-with-us:hover:not(:disabled){background:linear-gradient(135deg,#059669,#047857);box-shadow:0 4px 12px #10b98166;transform:translateY(-1px)}.auto_cancellation_wrapper ::ng-deep .btn__spinner{width:18px;height:18px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.auto_cancellation_wrapper ::ng-deep .isNusk .main_btn,.auto_cancellation_wrapper ::ng-deep .isNusk .btn--primary{background:linear-gradient(135deg,var(--almatar-nusuk-primary),var(--almatar-nusuk-primary))!important;border-radius:33px!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: LoaderModule }, { kind: "component", type: LoaderComponent, selector: "app-loader", inputs: ["loading", "message"] }, { kind: "component", type: AsideComponent, selector: "almatar-aside", inputs: ["asideTitle", "showHeader", "headerTemplate", "whiteHeader", "showAside", "width", "modal", "closable", "transitionOptions", "closeIconTemplate", "interceptClose"], outputs: ["onClose", "closeRequested"] }, { kind: "component", type: AlmatarHotelAutoCancellationSelectRoomsComponent, selector: "almatar-hotel-auto-cancellation-select-rooms", inputs: ["packages", "hotelInfo", "bookingId", "hotelId", "currencyCode"], outputs: ["continueToCancel", "closeModal", "nonRefundableSubmitSuccess", "chatWithAgent"] }, { kind: "component", type: AlmatarHotelAutoCancellationRefundSummaryComponent, selector: "almatar-hotel-auto-cancellation-refund-summary", inputs: ["selectedRooms", "bookingId", "hotelId", "hotelInfo", "selectionSkipped"], outputs: ["keepRoom", "selectRefundOption", "closeModal"] }, { kind: "component", type: AlmatarHotelAutoCancellationRefundMethodComponent, selector: "almatar-hotel-auto-cancellation-refund-method", inputs: ["refundDetailsData", "currencyObj", "refundMethod", "bookingId", "isCancelling", "showBack"], outputs: ["methodSelected", "backRequested", "confirmRequested", "closeModal"] }, { kind: "ngmodule", type: AlmatarIconModule }, { kind: "component", type: ResultOverlayComponent, selector: "app-result-overlay", inputs: ["data", "show"], outputs: ["confirm", "cancel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5501
+ ], ngImport: i0, template: "@if (showAside()) {\n <almatar-aside\n\n class=\"auto_cancellation_wrapper\"\n [asideTitle]=\"asideTitle()\"\n [showAside]=\"showAside()\"\n [whiteHeader]=\"true\"\n [showHeader]=\"false\"\n (onClose)=\"onCloseModal()\"\n >\n <!-- Custom Header with 60px height -->\n <div class=\"auto_cancellation__header\">\n @if (canGoBack()) {\n <button class=\"auto_cancellation__header-back\" (click)=\"onGoBack()\" type=\"button\">\n <i class=\"pi pi-arrow-left\"></i>\n </button>\n }\n <div class=\"auto_cancellation__header-content\">\n @if (currentStep() === stepEnum.selectRooms) {\n <h2 class=\"auto_cancellation__header-title\">\n @if (isSinglePackage()) {\n Cancel Rooms\n } @else {\n Select Rooms\n }\n </h2>\n <p class=\"auto_cancellation__header-subtitle\">\n @if (isSinglePackage()) {\n Review and confirm your cancellation\n } @else {\n Choose which room(s) you'd like to cancel\n }\n </p>\n } @else if (currentStep() === stepEnum.refundSummary) {\n <h2 class=\"auto_cancellation__header-title\">Cancel Booking Request</h2>\n <p class=\"auto_cancellation__header-subtitle\">Review your refund summary and proceed</p>\n } @else if (currentStep() === stepEnum.refundMethod) {\n <h2 class=\"auto_cancellation__header-title\">Select Refund Method</h2>\n <p class=\"auto_cancellation__header-subtitle\">Choose how you'd like to receive your refund</p>\n } @else {\n <h2 class=\"auto_cancellation__header-title\">{{ asideTitle() }}</h2>\n <p class=\"auto_cancellation__header-subtitle\">Complete your booking cancellation</p>\n }\n </div>\n <button class=\"auto_cancellation__header-close\" (click)=\"onCloseModal()\" type=\"button\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n\n <div class=\"auto_cancellation__component\" [class.isNusk]=\"isNusk()\">\n @if (isLoading()) {\n <div class=\"auto_cancellation__loading\">\n <app-loader></app-loader>\n </div>\n }\n\n @if (currentStep() === stepEnum.selectRooms && !isLoading()) {\n <almatar-hotel-auto-cancellation-select-rooms\n [packages]=\"packages()\"\n [hotelInfo]=\"hotelInfo()\"\n [bookingId]=\"selectedHotelBooking()?.bookingId\"\n [hotelId]=\"selectedHotelBooking()?.hotelId\"\n [currencyCode]=\"selectedHotelBooking()?.currencyCode || 'SAR'\"\n (continueToCancel)=\"onContinueToCancel($event)\"\n (closeModal)=\"onCloseModal()\"\n (nonRefundableSubmitSuccess)=\"onNonRefundableSubmitSuccess($event)\"\n (chatWithAgent)=\"onChatWithAgent()\"\n />\n }\n\n @if (currentStep() === stepEnum.refundSummary && !isLoading()) {\n <almatar-hotel-auto-cancellation-refund-summary\n [selectedRooms]=\"autoCancellationService.selectedRooms()\"\n [bookingId]=\"selectedHotelBooking()?.bookingId\"\n [hotelId]=\"selectedHotelBooking()?.id || selectedHotelBooking()?.hpk\"\n [hotelInfo]=\"hotelInfo()\"\n [selectionSkipped]=\"packages().length <= 1\"\n (keepRoom)=\"onKeepRoom()\"\n (selectRefundOption)=\"onSelectRefundOption($event)\"\n (closeModal)=\"onCloseModal()\"\n />\n }\n\n @if (currentStep() === stepEnum.refundMethod && !isLoading()) {\n <almatar-hotel-auto-cancellation-refund-method\n [refundDetailsData]=\"refundDetailsData()\"\n [currencyObj]=\"currentCurrency()\"\n [refundMethod]=\"refundMethod()\"\n [isCancelling]=\"isCancelling()\"\n (methodSelected)=\"onMethodSelected($event)\"\n (backRequested)=\"onBackFromRefundMethod()\"\n (confirmRequested)=\"confirmCancellation()\"\n (closeModal)=\"onCloseModal()\"\n [bookingId]=\"selectedHotelBooking()?.bookingId\"\n />\n }\n </div>\n </almatar-aside>\n}\n\n@if (showErrorModal()) {\n <div\n almatar-manage-booking-failed-modal\n [errorMessage]=\"hotelManageError()\"\n (cancelEvent)=\"closeAllModals()\"\n (confirmEvent)=\"closeAllModals()\"\n ></div>\n}\n\n@if (showResultOverlay() && resultOverlayData()) {\n <app-result-overlay\n [data]=\"resultOverlayData()!\"\n [show]=\"showResultOverlay()\"\n (confirm)=\"onResultOverlayConfirm()\"\n (cancel)=\"onResultOverlayCancel()\"\n />\n}\n", styles: [".auto_cancellation__header{height:60px;display:flex;align-items:center;justify-content:space-between;padding:0 20px;background:#fff;border-bottom:1px solid #e2e8f0;flex-shrink:0;gap:16px}.auto_cancellation__header-content{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0}.auto_cancellation__header-title{font-size:calc(18 * var(--almatar-font-size-ratio, 1) * 1px);font-weight:700;color:#1e293b;margin:0;line-height:1.3}.auto_cancellation__header-subtitle{font-size:calc(12 * var(--almatar-font-size-ratio, 1) * 1px);font-weight:400;color:#64748b;margin:0;line-height:1.4}.auto_cancellation__header-back{width:32px;height:32px;display:flex;align-items:center;justify-content:center;background:transparent;border:none;border-radius:6px;cursor:pointer;flex-shrink:0;transition:all .2s ease;margin-right:8px}.auto_cancellation__header-back i{font-size:18px;color:#64748b}.auto_cancellation__header-back:hover{background:#f1f5f9}.auto_cancellation__header-back:hover i{color:#1e293b}.auto_cancellation__header-back:active{background:#e2e8f0}.auto_cancellation__header-close{width:32px;height:32px;display:flex;align-items:center;justify-content:center;background:transparent;border:none;border-radius:6px;cursor:pointer;flex-shrink:0;transition:all .2s ease}.auto_cancellation__header-close i{font-size:18px;color:#64748b}.auto_cancellation__header-close:hover{background:#f1f5f9}.auto_cancellation__header-close:hover i{color:#1e293b}.auto_cancellation__header-close:active{background:#e2e8f0}.auto_cancellation__component{height:calc(100% - 60px)}.auto_cancellation__loading{display:flex;align-items:center;justify-content:center;height:100%;min-height:200px}.auto_cancellation_wrapper ::ng-deep almatar-hotel-auto-cancellation{display:block;height:100%}.auto_cancellation_wrapper ::ng-deep .p-sidebar-header{display:none!important}.auto_cancellation_wrapper ::ng-deep .p-sidebar-content{padding-top:0!important}.auto_cancellation_wrapper ::ng-deep .p-sidebar-close{display:none!important}.auto_cancellation_wrapper ::ng-deep .almatar_aside .p-dialog-content{background:var(--almatar-neutral-surface-screen-bg)}.auto_cancellation_wrapper ::ng-deep .main_btn,.auto_cancellation_wrapper ::ng-deep .btn{min-height:48px;padding:12px 24px;border-radius:10px;font-size:15px;font-weight:600;border:none;cursor:pointer;transition:all .2s ease;display:inline-flex;align-items:center;justify-content:center;gap:8px;position:relative;overflow:hidden}.auto_cancellation_wrapper ::ng-deep .main_btn:disabled,.auto_cancellation_wrapper ::ng-deep .btn:disabled{opacity:.5;cursor:not-allowed}.auto_cancellation_wrapper ::ng-deep .main_btn,.auto_cancellation_wrapper ::ng-deep .btn--primary{background:linear-gradient(135deg,#3b82f6,#2563eb);color:#fff;box-shadow:0 2px 8px #3b82f64d}.auto_cancellation_wrapper ::ng-deep .main_btn:hover:not(:disabled),.auto_cancellation_wrapper ::ng-deep .btn--primary:hover:not(:disabled){background:linear-gradient(135deg,#2563eb,#1d4ed8);box-shadow:0 4px 12px #3b82f666;transform:translateY(-1px)}.auto_cancellation_wrapper ::ng-deep .main_btn:active:not(:disabled),.auto_cancellation_wrapper ::ng-deep .btn--primary:active:not(:disabled){transform:translateY(0);box-shadow:0 2px 6px #3b82f64d}.auto_cancellation_wrapper ::ng-deep .btn--secondary{background:#f1f5f9;color:#1e293b;border:1px solid #e2e8f0}.auto_cancellation_wrapper ::ng-deep .btn--secondary:hover:not(:disabled){background:#e2e8f0;border-color:#cbd5e1;transform:translateY(-1px)}.auto_cancellation_wrapper ::ng-deep .btn--secondary:active:not(:disabled){background:#cbd5e1;transform:translateY(0)}.auto_cancellation_wrapper ::ng-deep .btn--danger{background:linear-gradient(135deg,#ef4444,#dc2626);color:#fff;box-shadow:0 2px 8px #ef44444d}.auto_cancellation_wrapper ::ng-deep .btn--danger:hover:not(:disabled){background:linear-gradient(135deg,#dc2626,#b91c1c);box-shadow:0 4px 12px #ef444466;transform:translateY(-1px)}.auto_cancellation_wrapper ::ng-deep .btn--danger:active:not(:disabled){transform:translateY(0);box-shadow:0 2px 6px #ef44444d}.auto_cancellation_wrapper ::ng-deep .main_btn--chat-with-us{background:linear-gradient(135deg,#10b981,#059669);box-shadow:0 2px 8px #10b9814d}.auto_cancellation_wrapper ::ng-deep .main_btn--chat-with-us:hover:not(:disabled){background:linear-gradient(135deg,#059669,#047857);box-shadow:0 4px 12px #10b98166;transform:translateY(-1px)}.auto_cancellation_wrapper ::ng-deep .btn__spinner{width:18px;height:18px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.auto_cancellation_wrapper ::ng-deep .isNusk .main_btn,.auto_cancellation_wrapper ::ng-deep .isNusk .btn--primary{background:linear-gradient(135deg,var(--almatar-nusuk-primary),var(--almatar-nusuk-primary))!important;border-radius:33px!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: LoaderModule }, { kind: "component", type: LoaderComponent, selector: "app-loader", inputs: ["loading", "message"] }, { kind: "component", type: AsideComponent, selector: "almatar-aside", inputs: ["asideTitle", "showHeader", "headerTemplate", "whiteHeader", "showAside", "width", "modal", "closable", "transitionOptions", "interceptClose"], outputs: ["onClose", "closeRequested"] }, { kind: "component", type: AlmatarHotelAutoCancellationSelectRoomsComponent, selector: "almatar-hotel-auto-cancellation-select-rooms", inputs: ["packages", "hotelInfo", "bookingId", "hotelId", "currencyCode"], outputs: ["continueToCancel", "closeModal", "nonRefundableSubmitSuccess", "chatWithAgent"] }, { kind: "component", type: AlmatarHotelAutoCancellationRefundSummaryComponent, selector: "almatar-hotel-auto-cancellation-refund-summary", inputs: ["selectedRooms", "bookingId", "hotelId", "hotelInfo", "selectionSkipped"], outputs: ["keepRoom", "selectRefundOption", "closeModal"] }, { kind: "component", type: AlmatarHotelAutoCancellationRefundMethodComponent, selector: "almatar-hotel-auto-cancellation-refund-method", inputs: ["refundDetailsData", "currencyObj", "refundMethod", "bookingId", "isCancelling", "showBack"], outputs: ["methodSelected", "backRequested", "confirmRequested", "closeModal"] }, { kind: "ngmodule", type: AlmatarIconModule }, { kind: "component", type: ResultOverlayComponent, selector: "app-result-overlay", inputs: ["data", "show"], outputs: ["confirm", "cancel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5486
5502
  }
5487
5503
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AlmatarHotelAutoCancellationComponent, decorators: [{
5488
5504
  type: Component,
@@ -6058,6 +6074,1880 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
6058
6074
  args: [{ selector: 'app-sbd-add-note-dialog', standalone: true, imports: [DialogModule, ButtonModule, FormsModule, NgSelectModule], template: "<p-dialog\n [visible]=\"visible()\"\n (visibleChange)=\"$event ? null : closed.emit()\"\n [modal]=\"true\"\n [blockScroll]=\"true\"\n [draggable]=\"false\"\n [resizable]=\"false\"\n [style]=\"{ width: '480px' }\"\n styleClass=\"sbd-add-note-dialog-wrapper\"\n header=\"Add Note\">\n\n <div class=\"and\">\n\n <!-- Note type -->\n <div class=\"and__field\">\n <label class=\"and__label\">Note type</label>\n <div class=\"and__type-group\">\n @for (opt of actionOptions; track opt.value) {\n <button\n type=\"button\"\n class=\"and__type-btn\"\n [class.and__type-btn--active]=\"action() === opt.value\"\n (click)=\"action.set(opt.value)\">\n <i class=\"{{ opt.icon }}\"></i>\n {{ opt.label }}\n </button>\n }\n </div>\n </div>\n\n <!-- Message -->\n <div class=\"and__field\">\n <label class=\"and__label\">\n Message\n <span class=\"and__char-count\">{{ message().length }} / 1000</span>\n </label>\n <textarea\n class=\"and__textarea\"\n [ngModel]=\"message()\"\n (ngModelChange)=\"message.set($event)\"\n placeholder=\"Write your note here...\"\n rows=\"6\"\n maxlength=\"1000\"\n [disabled]=\"submitting()\">\n </textarea>\n </div>\n\n </div>\n\n <ng-template pTemplate=\"footer\">\n <button pButton label=\"Cancel\" icon=\"pi pi-times\"\n class=\"p-button-outlined p-button-secondary p-button-sm\"\n [disabled]=\"submitting()\"\n (click)=\"closed.emit()\">\n </button>\n <button pButton label=\"Submit Note\"\n [icon]=\"submitting() ? 'pi pi-spin pi-spinner' : 'pi pi-check'\"\n class=\"p-button-sm sbd-note-submit\"\n [disabled]=\"!message().trim() || submitting()\"\n (click)=\"submit()\">\n </button>\n </ng-template>\n\n</p-dialog>\n", styles: [".and{display:flex;flex-direction:column;gap:20px;padding:4px 0}.and__field{display:flex;flex-direction:column;gap:8px}.and__label{display:flex;align-items:center;justify-content:space-between;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#64748b}.and__char-count{font-size:11px;font-weight:500;text-transform:none;letter-spacing:0;color:#94a3b8}.and__type-group{display:flex;gap:8px}.and__type-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:9px 12px;border:1.5px solid #e2e8f0;border-radius:8px;background:#f8fafc;color:#64748b;font-size:12.5px;font-weight:600;cursor:pointer;transition:all .15s ease}.and__type-btn .pi{font-size:13px}.and__type-btn:hover{border-color:#94a3b8;background:#f1f5f9;color:#334155}.and__type-btn--active{border-color:var(--sbd-primary, #3b82f6);background:#eff6ff;color:#1d4ed8}.and__type-btn--active:hover{border-color:#2563eb;background:#dbeafe}.and__textarea{width:100%;resize:vertical;padding:10px 12px;border:1.5px solid #e2e8f0;border-radius:8px;font-size:13.5px;font-family:inherit;color:#1e2e4a;background:#fff;line-height:1.55;transition:border-color .15s ease;box-sizing:border-box}.and__textarea::placeholder{color:#94a3b8}.and__textarea:focus{outline:none;border-color:var(--sbd-primary, #3b82f6);box-shadow:0 0 0 3px color-mix(in srgb,var(--sbd-primary, #3b82f6) 10%,transparent)}.and__textarea:disabled{background:#f8fafc;color:#94a3b8;cursor:not-allowed}.sbd-note-submit{background:var(--sbd-primary, #007ad9);border-color:var(--sbd-primary, #007ad9)}.sbd-note-submit:enabled:hover{background:color-mix(in srgb,var(--sbd-primary, #007ad9) 88%,#000);border-color:color-mix(in srgb,var(--sbd-primary, #007ad9) 88%,#000)}\n"] }]
6059
6075
  }], ctorParameters: () => [] });
6060
6076
 
6077
+ /**
6078
+ * Port of the web's `FlightManageBookingService.navigateToOnlineBooking()`
6079
+ * (almatar-consumer-apps) — builds the query params of
6080
+ * `/flights/manage-booking/list`, where the web continues the reissue
6081
+ * (search -> view-price -> choose-flight -> payment).
6082
+ * Keep the param names/values identical to the web.
6083
+ */
6084
+ const REISSUE_WEB_PATH = '/flights/manage-booking/list/';
6085
+ /** Same strings as the web's `FlightManageBookingAsideTabEnum`. */
6086
+ const SERVICE_NAME = {
6087
+ change_date: 'change-travel-date-time',
6088
+ change_route: 'change-flight-route',
6089
+ };
6090
+ // ── date helpers ────────────────────────────────────────────────────────────
6091
+ /** `moment.utc(str).valueOf()` — strings without a timezone are read as UTC. */
6092
+ function utcMs(value) {
6093
+ const hasTz = /[zZ]$|[+-]\d{2}:?\d{2}$/.test(value);
6094
+ return new Date(hasTz ? value : `${value}Z`).getTime();
6095
+ }
6096
+ /** Web's `getAbsoluteDate(date)` -> `YYYY-MM-DD` of the date part. */
6097
+ function dateOnly(value) {
6098
+ return value.slice(0, 10);
6099
+ }
6100
+ /** Web's `getAbsoluteDate(date, true)` -> UTC midnight of that calendar day. */
6101
+ function absoluteMs(value) {
6102
+ const [y, m, d] = dateOnly(value).split('-').map(Number);
6103
+ return Date.UTC(y, m - 1, d);
6104
+ }
6105
+ function capitalize(value) {
6106
+ return value ? value.charAt(0).toUpperCase() + value.slice(1).toLowerCase() : value;
6107
+ }
6108
+ // ── booking_info -> context ─────────────────────────────────────────────────
6109
+ function inferTripType(bi, legs) {
6110
+ const explicit = bi?.IteneraryTripType;
6111
+ if (explicit === 'OneWay' || explicit === 'RoundTrip' || explicit === 'MultiCity')
6112
+ return explicit;
6113
+ if (legs.length <= 1)
6114
+ return 'OneWay';
6115
+ if (legs.length === 2 && legs[1].direction === 'Return')
6116
+ return 'RoundTrip';
6117
+ return 'MultiCity';
6118
+ }
6119
+ function countPassengers(bi, type) {
6120
+ const list = bi?.passenger_info?.passengers ?? [];
6121
+ return list.filter(p => p?.type === type).length;
6122
+ }
6123
+ /**
6124
+ * Normalizes the dashboard's `booking_info` (from `/v1/fcr/booking/information`)
6125
+ * into what the URL builder needs. Reads defensively: any field missing in the
6126
+ * BO response falls back to an empty value.
6127
+ */
6128
+ function buildReissueBookingContext(bookingData, bookingId) {
6129
+ const bi = bookingData?.data?.booking_info;
6130
+ const legsRaw = bi?.leg_info ?? [];
6131
+ if (!legsRaw.length)
6132
+ return null;
6133
+ const airline = bi?.airline_info ?? [];
6134
+ const legs = legsRaw.map((l, i) => {
6135
+ const segs = airline[i]?.Segments ?? [];
6136
+ const first = segs[0] ?? {};
6137
+ const last = segs[segs.length - 1] ?? first;
6138
+ // Some bookings come back with a sparse `leg_info` — fall back to the
6139
+ // first/last segment (airline_info, then duration/terminal) for the route and times.
6140
+ const durSegs = bi?.duration?.legs?.[i]?.Segments ?? [];
6141
+ const termSegs = bi?.terminal?.[i]?.Segments ?? [];
6142
+ const firstAny = { ...(termSegs[0] ?? {}), ...(durSegs[0] ?? {}), ...first };
6143
+ const lastAny = {
6144
+ ...(termSegs[termSegs.length - 1] ?? {}),
6145
+ ...(durSegs[durSegs.length - 1] ?? {}),
6146
+ ...last,
6147
+ };
6148
+ const originCode = l.LegDepartureAirportCode ?? firstAny.DepartureAirportCode ?? firstAny.SegmentDepartureAirportCode ?? '';
6149
+ const destinationCode = l.LegArrivalAirportCode ?? lastAny.ArrivalAirportCode ?? lastAny.SegmentArrivalAirportCode ?? '';
6150
+ return {
6151
+ // No id in the response -> a positional placeholder so the dialog still works.
6152
+ legId: l.LegID ?? l.legId ?? l.LegId ?? `leg-${i}`,
6153
+ direction: l.LegDirectionality ?? '',
6154
+ originCode,
6155
+ originName: l.LegDepartureAirportName ?? firstAny.DepartureAirportName ?? '',
6156
+ originCityName: l.LegDepartureCityName ?? firstAny.SegmentDepartureCityName ?? firstAny.DepartureAirportName ?? originCode,
6157
+ originCountryName: l.LegDepartureCountryName ?? firstAny.SegmentDepartureCountryName ?? '',
6158
+ destinationCode,
6159
+ destinationName: l.LegArrivalAirportName ?? lastAny.ArrivalAirportName ?? '',
6160
+ destinationCityName: l.LegArrivalCityName ?? lastAny.SegmentArrivalCityName ?? lastAny.ArrivalAirportName ?? destinationCode,
6161
+ destinationCountryName: l.LegArrivalCountryName ?? lastAny.SegmentArrivalCountryName ?? '',
6162
+ departure: l.LegDepartureDateTime ?? firstAny.DepartureDateTime ?? firstAny.SegmentDepartureDateTime ?? firstAny.DepartureTime ?? '',
6163
+ arrival: l.LegArrivalDateTime ?? lastAny.ArrivalDateTime ?? lastAny.SegmentArrivalDateTime ?? lastAny.ArrivalTime ?? '',
6164
+ airline: (l.LegAirlinesNames ?? [])[0] ?? first.AirlineName ?? '',
6165
+ stops: (l.Stops ?? []).length,
6166
+ flightNo: first.AirlineCode ? `${first.AirlineCode}${first.FlightNumber ?? ''}` : '',
6167
+ };
6168
+ });
6169
+ const firstLegRaw = legsRaw[0];
6170
+ const firstSeg = airline[0]?.Segments?.[0] ?? {};
6171
+ return {
6172
+ bookingId: String(bookingId),
6173
+ tripType: inferTripType(bi, legs),
6174
+ legs,
6175
+ adults: countPassengers(bi, 'ADT'),
6176
+ children: countPassengers(bi, 'CNN'),
6177
+ infants: countPassengers(bi, 'INF'),
6178
+ // The BO response may carry no cabin at all (sparse bookings) — default so the web search still gets a class.
6179
+ cabinName: firstLegRaw.LegCabins?.[0]?.Name ?? 'Economy',
6180
+ cabinCode: firstLegRaw.LegCabins?.[0]?.Code ?? 'Y',
6181
+ currency: bi?.currency ?? bi?.payment_status?.currency ?? '',
6182
+ airlineCode: firstSeg.AirlineCode ?? '',
6183
+ flightNumber: String(firstSeg.FlightNumber ?? ''),
6184
+ airlineName: (firstLegRaw.LegAirlinesNames ?? [])[0] ?? firstSeg.AirlineName ?? '',
6185
+ totalStops: (firstLegRaw.Stops ?? []).length,
6186
+ };
6187
+ }
6188
+ // ── URL builder ─────────────────────────────────────────────────────────────
6189
+ function airportCityLabel(name, country, code) {
6190
+ return `${name || code}${country ? ', ' + country : ''}(${code})`;
6191
+ }
6192
+ function otherLegOf(ctx, legId) {
6193
+ const other = ctx.legs.find(l => l.legId !== legId);
6194
+ return other
6195
+ ? {
6196
+ legId: other.legId,
6197
+ departDateTime: other.departure,
6198
+ destinationCode: other.destinationCode,
6199
+ originCode: other.originCode,
6200
+ isReturn: other.direction === 'Return',
6201
+ }
6202
+ : undefined;
6203
+ }
6204
+ function buildReissueSearchParams(rawCtx, selection, postBookingId) {
6205
+ // The BO booking information may carry no leg dates at all. Without an original date the
6206
+ // web's `departure` / `return` / `updatingLegs` would be NaN, so fall back to the date the
6207
+ // agent picked (or today) — only affects sparse bookings; real ones have `LegDepartureDateTime`.
6208
+ const fallbackDate = `${selection.legs.find(c => c.departDate)?.departDate ?? new Date().toLocaleDateString('en-CA')}T00:00:00`;
6209
+ const ctx = {
6210
+ ...rawCtx,
6211
+ legs: rawCtx.legs.map(l => (l.departure ? l : { ...l, departure: fallbackDate })),
6212
+ };
6213
+ const isDate = selection.service === 'change_date';
6214
+ const leg0 = ctx.legs[0];
6215
+ const changes = selection.legs;
6216
+ const legOf = (c) => ctx.legs.find(l => l.legId === c.legId) ?? leg0;
6217
+ // `config.legs` in the web.
6218
+ const legsPayload = changes.map(c => {
6219
+ const orig = legOf(c);
6220
+ if (isDate) {
6221
+ return {
6222
+ legId: c.legId,
6223
+ ...(ctx.tripType === 'MultiCity' ? { touched: true } : {}),
6224
+ departDateTime: dateOnly(c.departDate ?? orig.departure),
6225
+ destinationCode: orig.destinationCode,
6226
+ originCode: orig.originCode,
6227
+ };
6228
+ }
6229
+ return {
6230
+ legId: c.legId,
6231
+ originCode: c.origin?.id ?? orig.originCode,
6232
+ originCityName: c.origin?.city_name || c.origin?.name || orig.originCityName,
6233
+ destinationCode: c.destination?.id ?? orig.destinationCode,
6234
+ destinationCityName: c.destination?.city_name || c.destination?.name || orig.destinationCityName,
6235
+ departDateTime: orig.departure,
6236
+ };
6237
+ });
6238
+ // `routeConfig` in the web.
6239
+ const first = changes[0];
6240
+ const routeOrigin = !isDate ? first?.origin : undefined;
6241
+ const routeDest = !isDate ? first?.destination : undefined;
6242
+ const routeDeparture = isDate ? `${dateOnly(changes[0].departDate ?? leg0.departure)}T00:00:00` : undefined;
6243
+ const routeReturn = isDate && changes.length > 1
6244
+ ? `${dateOnly(changes[1].departDate ?? ctx.legs[1]?.departure ?? '')}T00:00:00`
6245
+ : undefined;
6246
+ const origin = routeOrigin?.id ?? leg0.originCode;
6247
+ const destination = routeDest?.id ?? leg0.destinationCode;
6248
+ const className = ctx.cabinName.toUpperCase().includes('ECONOMY')
6249
+ ? capitalize(ctx.cabinName.replace(/\s+/g, ''))
6250
+ : ctx.cabinName;
6251
+ const params = {
6252
+ flightSearchId: ctx.bookingId,
6253
+ category: 'flights',
6254
+ id: `${ctx.airlineCode}-${ctx.flightNumber}`,
6255
+ origin,
6256
+ originCity: routeOrigin
6257
+ ? routeOrigin.name || `${routeOrigin.city_name}, ${routeOrigin.country_name}`
6258
+ : airportCityLabel(leg0.originName, leg0.originCountryName, leg0.originCode),
6259
+ originCityName: routeOrigin?.city_name ?? leg0.originCityName,
6260
+ originCountryName: routeOrigin?.country_name ?? leg0.originCountryName,
6261
+ destinationCityName: routeDest?.city_name ?? leg0.destinationCityName,
6262
+ destinationCity: routeDest
6263
+ ? routeDest.name || `${routeDest.city_name}, ${routeDest.country_name}`
6264
+ : airportCityLabel(leg0.destinationName, leg0.destinationCountryName, leg0.destinationCode),
6265
+ destinationCountryName: routeDest?.country_name ?? leg0.destinationCountryName,
6266
+ destination,
6267
+ departureFlightAirlines: ctx.airlineName,
6268
+ adult: ctx.adults,
6269
+ child: ctx.children,
6270
+ lapinfant: ctx.infants,
6271
+ class: className,
6272
+ className: ctx.cabinCode,
6273
+ departure: utcMs(routeDeparture ?? leg0.departure),
6274
+ infantonchair: 0,
6275
+ originType: routeOrigin?.type ?? 'city',
6276
+ destinationType: routeDest?.type ?? 'city',
6277
+ isDirect: 'false',
6278
+ serviceName: SERVICE_NAME[selection.service],
6279
+ currency: ctx.currency,
6280
+ classType: ctx.cabinCode,
6281
+ connection: ctx.tripType,
6282
+ directFlights: ctx.totalStops === 0,
6283
+ flexibleDates: false,
6284
+ legs: JSON.stringify(legsPayload),
6285
+ // Lets the results page show an "UPDATING NOW" banner per changed leg.
6286
+ updatingLegs: JSON.stringify(changes.map(c => {
6287
+ const o = legOf(c);
6288
+ return {
6289
+ legId: o.legId,
6290
+ direction: o.direction,
6291
+ originCode: o.originCode,
6292
+ destinationCode: o.destinationCode,
6293
+ originCityName: o.originCityName,
6294
+ destinationCityName: o.destinationCityName,
6295
+ originalDeparture: utcMs(o.departure),
6296
+ };
6297
+ })),
6298
+ };
6299
+ if (ctx.tripType === 'RoundTrip') {
6300
+ const secondLeg = ctx.legs[1];
6301
+ params['return'] = routeReturn
6302
+ ? utcMs(routeReturn)
6303
+ : secondLeg
6304
+ ? utcMs(secondLeg.departure) // the web parses this one in the customer's timezone; read as UTC here so it doesn't depend on the agent's timezone
6305
+ : utcMs(String(legsPayload[0].departDateTime));
6306
+ if (changes.length === 1) {
6307
+ const other = otherLegOf(ctx, changes[0].legId);
6308
+ if (isDate && other && !other.isReturn) {
6309
+ params['departure'] = utcMs(other.departDateTime);
6310
+ params['return'] = utcMs(String(legsPayload[0].departDateTime));
6311
+ }
6312
+ if (other)
6313
+ params['extraLeg'] = JSON.stringify(other);
6314
+ }
6315
+ }
6316
+ if (ctx.tripType === 'MultiCity') {
6317
+ let cities;
6318
+ if (isDate) {
6319
+ const changedDate = new Map(changes.map(c => [c.legId, c.departDate]));
6320
+ cities = ctx.legs.map(l => ({
6321
+ departure: absoluteMs(changedDate.get(l.legId) || l.departure),
6322
+ destination: l.destinationCode,
6323
+ destinationType: l.destinationCode,
6324
+ origin: l.originCode,
6325
+ originType: l.originCode,
6326
+ destinationCity: l.destinationCityName,
6327
+ destinationCityName: l.destinationCityName,
6328
+ destinationName: l.destinationCityName,
6329
+ destinationCountryName: l.destinationCountryName,
6330
+ originCity: l.originCityName,
6331
+ originCityName: l.originCityName,
6332
+ originName: l.originCityName,
6333
+ originCountryName: l.originCountryName,
6334
+ }));
6335
+ }
6336
+ else {
6337
+ cities = changes.map(c => {
6338
+ const o = legOf(c);
6339
+ const from = c.origin;
6340
+ const to = c.destination;
6341
+ return {
6342
+ departure: utcMs(o.departure),
6343
+ destination: to?.id ?? o.destinationCode,
6344
+ destinationType: to?.type ?? o.destinationCode,
6345
+ origin: from?.id ?? o.originCode,
6346
+ originType: from?.id ?? o.originCode,
6347
+ destinationCity: to?.name ?? o.destinationCityName,
6348
+ destinationCityName: to?.name ?? o.destinationCityName,
6349
+ destinationName: to?.name ?? o.destinationCityName,
6350
+ destinationCountryName: to?.country_name ?? o.destinationCountryName,
6351
+ originCity: from?.name ?? o.originCityName,
6352
+ originCityName: from?.name ?? o.originCityName,
6353
+ originName: from?.name ?? o.originCityName,
6354
+ originCountryName: from?.country_name ?? o.originCountryName,
6355
+ };
6356
+ });
6357
+ }
6358
+ params['departure'] = cities[0].departure;
6359
+ params['cities'] = JSON.stringify(cities);
6360
+ }
6361
+ params['bookingId'] = ctx.bookingId;
6362
+ params['postBookingId'] = postBookingId;
6363
+ return Object.fromEntries(Object.entries(params)
6364
+ .filter(([, v]) => v !== undefined && v !== null)
6365
+ .map(([k, v]) => [k, String(v)]));
6366
+ }
6367
+ function buildReissueSearchUrl(webBaseUrl, ctx, selection, postBookingId) {
6368
+ const qs = new URLSearchParams(buildReissueSearchParams(ctx, selection, postBookingId));
6369
+ return `${webBaseUrl.replace(/\/+$/, '')}${REISSUE_WEB_PATH}?${qs.toString()}`;
6370
+ }
6371
+ /**
6372
+ * Adds the host's auth parameters (for example its token) to the web link: in the query string, or in the `#fragment`
6373
+ * (which browsers do not send to servers or in the Referer). Empty values are skipped.
6374
+ */
6375
+ function appendAuthParams(url, params, where = 'query') {
6376
+ const entries = Object.entries(params ?? {}).filter(([, value]) => !!value);
6377
+ if (!entries.length)
6378
+ return url;
6379
+ const target = new URL(url);
6380
+ if (where === 'hash') {
6381
+ const hash = new URLSearchParams(target.hash.replace(/^#/, ''));
6382
+ entries.forEach(([name, value]) => hash.set(name, value));
6383
+ target.hash = hash.toString();
6384
+ }
6385
+ else {
6386
+ entries.forEach(([name, value]) => target.searchParams.set(name, value));
6387
+ }
6388
+ return target.toString();
6389
+ }
6390
+
6391
+ /**
6392
+ * Query params the web always sends on `create-post-booking` (`getEntrySourceDeviceBrand()`): where the flow started
6393
+ * and on which device. Same defaults as the web; ask the backend which values BO should send.
6394
+ */
6395
+ const POST_BOOKING_PARAMS = { entrySource: 'navigation bar', device: 'web' };
6396
+ /**
6397
+ * Reissue APIs — the same endpoints the consumer web uses (the backend opens them for BO).
6398
+ */
6399
+ class SbdReissueService {
6400
+ constructor() {
6401
+ this.http = inject(HttpClient);
6402
+ this.config = inject(SBD_CONFIG);
6403
+ this.base = this.config.apiBase;
6404
+ }
6405
+ get headers() {
6406
+ return { platform: this.config.platform };
6407
+ }
6408
+ /** `POST /v2/manage-booking/flight/create-post-booking/{bookingId}` -> `postBookingId`. */
6409
+ createPostBooking(bookingId) {
6410
+ return this.http
6411
+ .post(`${this.base}/v2/manage-booking/flight/create-post-booking/${bookingId}`, null, // the web sends no body on this call
6412
+ { headers: this.headers, params: POST_BOOKING_PARAMS })
6413
+ .pipe(map(res => res?.data?.postBookingId ?? ''));
6414
+ }
6415
+ /** `GET /v3/manage-booking/flight/reissue/availability/{bookingId}/{postBookingId}` -> `data.legs[]`. */
6416
+ getAvailability(bookingId, postBookingId, ctx) {
6417
+ return this.http.get(`${this.base}/v3/manage-booking/flight/reissue/availability/${bookingId}/${postBookingId}`, { headers: this.headers });
6418
+ }
6419
+ /**
6420
+ * `POST /v3/manage-booking/flight/reissue/upgrade-class/{bookingId}/{postBookingId}` — same call (and body) the
6421
+ * web makes to decide whether the "Upgrade Class" option is shown. Returns the unwrapped `data`.
6422
+ */
6423
+ getUpgradeClassOptions(bookingId, postBookingId, ctx) {
6424
+ const body = {
6425
+ serviceName: 'change-flight-class',
6426
+ legs: ctx.legs.map(l => ({
6427
+ legId: l.legId,
6428
+ legDepartureAirportCode: l.originCode,
6429
+ legArrivalAirportCode: l.destinationCode,
6430
+ legDepartureDate: l.departure,
6431
+ })),
6432
+ };
6433
+ return this.http
6434
+ .post(`${this.base}/v3/manage-booking/flight/reissue/upgrade-class/${bookingId}/${postBookingId}`, body, { headers: this.headers })
6435
+ .pipe(map(res => (res?.data ?? res) ?? null));
6436
+ }
6437
+ /** Same rule as the web's `hasUpgradeOptions()`: at least one option in any section. */
6438
+ hasUpgradeOptions(data) {
6439
+ if (!data)
6440
+ return false;
6441
+ if (data.fareOptionScope === 'leg')
6442
+ return (data.legs ?? []).some(l => (l.options?.length ?? 0) > 0);
6443
+ return (data.options?.length ?? 0) > 0;
6444
+ }
6445
+ /**
6446
+ * Airport picker for Change Route. Same typeahead endpoint the web and the backoffice already use
6447
+ * (`StaticDataService.getAirports`) — no extra backend work needed. Response: `data[].items[].items[]`.
6448
+ * An empty term makes no request (the dialog lists the booking's own airports meanwhile).
6449
+ */
6450
+ searchAirports(term) {
6451
+ const q = term.trim();
6452
+ if (!q)
6453
+ return of([]);
6454
+ return this.http
6455
+ .get(`${this.base}/v1/typeahead-search-service/suggestion/suggest/flights`, { params: { q }, headers: this.headers })
6456
+ .pipe(map(res => {
6457
+ const out = [];
6458
+ for (const group of res?.data ?? []) {
6459
+ for (const item of group.items ?? []) {
6460
+ if (item.items)
6461
+ out.push(...item.items);
6462
+ }
6463
+ }
6464
+ return out.map(a => ({
6465
+ id: a.id,
6466
+ name: a.name,
6467
+ city_name: a.city_name,
6468
+ country_name: a.country_name,
6469
+ type: a.type === 'airport' ? 'airport' : 'city',
6470
+ }));
6471
+ }), catchError$1(() => of([])));
6472
+ }
6473
+ // ── Upgrade Class ──────────────────────────────────────────────────────────
6474
+ /**
6475
+ * `POST /v3/manage-booking/flight/reissue/choose-flight/{bookingId}/{postBookingId}` — prices the chosen upgrade
6476
+ * option. Same endpoint/body the web uses; response `data` carries `itineraryAvailable`, `priceChanged`, `price`, `currency`.
6477
+ */
6478
+ chooseFlight(bookingId, postBookingId, body) {
6479
+ return this.http
6480
+ .post(`${this.base}/v3/manage-booking/flight/reissue/choose-flight/${bookingId}/${postBookingId}`, body, { headers: this.headers })
6481
+ .pipe(map(res => res?.data ?? {}));
6482
+ }
6483
+ /** `POST /v2/manage-booking/flight/book-now` — places a zero-total reissue (no payment needed). */
6484
+ bookNow(postBookingId) {
6485
+ return this.http.post(`${this.base}/v2/manage-booking/flight/book-now`, { postBookingId }, { headers: this.headers });
6486
+ }
6487
+ /**
6488
+ * Payment link of a reissue with an amount to pay. There is no separate API: the backend puts the link in the
6489
+ * cart of the post-booking (`GET /v2/manage-booking/flight/cart/{postBookingId}`, the call the web checkout reads).
6490
+ * The name of the field is not final yet: the first of the names below that exists is used, and the call fails
6491
+ * with a clear message when the cart has none.
6492
+ */
6493
+ getPaymentLink(postBookingId, amount = 0, currency = '') {
6494
+ return this.http
6495
+ .get(`${this.base}/v2/manage-booking/flight/cart/${postBookingId}`, {
6496
+ headers: this.headers,
6497
+ })
6498
+ .pipe(map(res => {
6499
+ const cart = res?.data ?? {};
6500
+ let url = cart['paymentLink'] ?? cart['link'] ?? cart['payment']?.link ?? cart['payment']?.paymentLink ?? cart['payment']?.url;
6501
+ if (!url)
6502
+ throw new Error('The cart has no payment link yet.');
6503
+ // The cart returns only a path ("/<paymentId>"): the customer page base comes from the host config.
6504
+ if (!/^https?:\/\//i.test(url)) {
6505
+ const linkBase = this.config.paymentLinkBase?.replace(/\/+$/, '');
6506
+ if (!linkBase) {
6507
+ throw new Error(`The cart returned a relative link (${url}) and no paymentLinkBase is configured.`);
6508
+ }
6509
+ url = `${linkBase}${url.startsWith('/') ? '' : '/'}${url}`;
6510
+ }
6511
+ return {
6512
+ url,
6513
+ amount: cart['payment']?.amount ?? amount,
6514
+ currency: cart['payment']?.currency ?? currency,
6515
+ expiresAt: cart['payment']?.expiryDate ?? cart['expiryDate'],
6516
+ };
6517
+ }));
6518
+ }
6519
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdReissueService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6520
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdReissueService, providedIn: 'root' }); }
6521
+ }
6522
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdReissueService, decorators: [{
6523
+ type: Injectable,
6524
+ args: [{ providedIn: 'root' }]
6525
+ }] });
6526
+
6527
+ /** Today as `YYYY-MM-DD` in the agent's timezone (what `<input type="date">` compares against). */
6528
+ function todayIso() {
6529
+ return new Date().toLocaleDateString('en-CA');
6530
+ }
6531
+ function formatReissueDate(value) {
6532
+ if (!value)
6533
+ return '—';
6534
+ const date = new Date(value);
6535
+ return Number.isNaN(date.getTime())
6536
+ ? value
6537
+ : date.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' });
6538
+ }
6539
+ /**
6540
+ * Expiry of the payment link as epoch ms. The backend sends `YYYY-MM-DD HH:mm:ss` with no timezone, read as UTC (to
6541
+ * be confirmed with the backend); a value that carries `Z` or an offset is read as it is. `null` when missing/invalid.
6542
+ */
6543
+ function parseLinkExpiry(value) {
6544
+ if (!value)
6545
+ return null;
6546
+ const hasZone = /(Z|[+-]\d{2}:?\d{2})$/i.test(value.trim());
6547
+ const ms = Date.parse(hasZone ? value : `${value.trim().replace(' ', 'T')}Z`);
6548
+ return Number.isNaN(ms) ? null : ms;
6549
+ }
6550
+ /** `mm:ss` (or `h:mm:ss` from one hour up) for a countdown. */
6551
+ function formatCountdown(ms) {
6552
+ const total = Math.max(0, Math.floor(ms / 1000));
6553
+ const h = Math.floor(total / 3600);
6554
+ const m = Math.floor((total % 3600) / 60);
6555
+ const s = total % 60;
6556
+ const pad = (n) => String(n).padStart(2, '0');
6557
+ return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${pad(m)}:${pad(s)}`;
6558
+ }
6559
+ function legTitle(leg) {
6560
+ return `${leg.direction || 'Leg'} · ${leg.originCode} → ${leg.destinationCode}`;
6561
+ }
6562
+ function airportOf(id, name, city, country) {
6563
+ return { id, name: name || city || id, city_name: city || id, country_name: country, type: 'airport' };
6564
+ }
6565
+ const originAirport = (l) => airportOf(l.originCode, l.originName, l.originCityName, l.originCountryName);
6566
+ const destinationAirport = (l) => airportOf(l.destinationCode, l.destinationName, l.destinationCityName, l.destinationCountryName);
6567
+ const compareAirports = (a, b) => a?.id === b?.id;
6568
+ function formatMoney(amount, currency) {
6569
+ return `${amount.toLocaleString('en-US', { maximumFractionDigits: 2 })} ${currency}`.trim();
6570
+ }
6571
+ /** `21 Oct` — same short date the cancel dialog shows on a leg card. */
6572
+ function formatShortDate(value) {
6573
+ if (!value)
6574
+ return '—';
6575
+ const date = new Date(value);
6576
+ return Number.isNaN(date.getTime()) ? value : date.toLocaleDateString('en-GB', { day: '2-digit', month: 'short' });
6577
+ }
6578
+ /** `01:55` straight from the `YYYY-MM-DDTHH:mm:ss` string (no timezone conversion). */
6579
+ function formatTime(value) {
6580
+ return value && value.length >= 16 && value[10] === 'T' ? value.slice(11, 16) : '—';
6581
+ }
6582
+ /**
6583
+ * The leg already departed. The booking gives no timezone for `LegDepartureDateTime` (airport local time), so this
6584
+ * compares with the agent's clock: it can be off by a few hours on the day of the flight, never by a day.
6585
+ */
6586
+ function isLegFlown(leg) {
6587
+ const time = leg.departure ? Date.parse(leg.departure) : NaN;
6588
+ return !Number.isNaN(time) && time < Date.now();
6589
+ }
6590
+
6591
+ function buildLegRow(leg, available, selected, unavailableReason = 'offline') {
6592
+ const flown = isLegFlown(leg);
6593
+ return {
6594
+ leg,
6595
+ direction: leg.direction,
6596
+ from: leg.originCode,
6597
+ to: leg.destinationCode,
6598
+ dateLabel: formatShortDate(leg.departure),
6599
+ depTime: formatTime(leg.departure),
6600
+ arrTime: formatTime(leg.arrival),
6601
+ stopsLabel: leg.stops > 0 ? `${leg.stops} stop${leg.stops > 1 ? 's' : ''}` : 'Direct',
6602
+ airlineLine: [leg.airline, leg.flightNo].filter(Boolean).join(' · '),
6603
+ flown,
6604
+ available: available && !flown,
6605
+ reason: flown ? 'flown' : available ? null : unavailableReason,
6606
+ selected: selected && !flown,
6607
+ };
6608
+ }
6609
+ const START_ERROR = 'Could not start the reissue request.';
6610
+ const AVAILABILITY_ERROR = 'Could not check availability for this booking.';
6611
+ /** `base (HTTP 500: message from the backend)` — so the agent (and we, while the APIs are new) can see why it failed. */
6612
+ function describeHttpError(base, e) {
6613
+ const err = e;
6614
+ // The backend sends both: `error` (the real reason, e.g. "Unauthorized!") and a generic `message`.
6615
+ const body = typeof err?.error === 'string' ? [err.error] : [err?.error?.error, err?.error?.message];
6616
+ const text = body.filter(Boolean).join(' — ').slice(0, 200);
6617
+ const detail = [err?.status ? `HTTP ${err.status}` : '', text].filter(Boolean).join(': ');
6618
+ return detail ? `${base} (${detail})` : base;
6619
+ }
6620
+ /**
6621
+ * State + rules of the Reissue dialog (one instance per dialog, provided by the dialog component).
6622
+ * The step components are presentational: they get view models from here through the dialog shell.
6623
+ */
6624
+ class SbdReissueStore {
6625
+ constructor() {
6626
+ this.api = inject(SbdReissueService);
6627
+ this.config = inject(SBD_CONFIG);
6628
+ this.today = todayIso();
6629
+ // ── state ─────────────────────────────────────────────────────────────────
6630
+ this.booking = signal(null);
6631
+ this.step = signal('service');
6632
+ this.loading = signal(false);
6633
+ this.error = signal(null);
6634
+ this.postBookingId = signal('');
6635
+ this.service = signal(null);
6636
+ /** Raw upgrade-class response; `null` when not loaded / not offered. Read by `SbdReissueUpgradeStore`. */
6637
+ this.upgradeOptions = signal(null);
6638
+ this.upgradeAvailable = computed(() => this.api.hasUpgradeOptions(this.upgradeOptions()));
6639
+ this.availableIds = signal(new Set());
6640
+ /** The availability call answered (so "no leg available" is a real answer, not "still loading"). */
6641
+ this.availabilityLoaded = signal(false);
6642
+ this.selectedLegIds = signal([]);
6643
+ /** The "select flight" step was skipped (one-way): Back from the edit step returns to the service list. */
6644
+ this.legsStepSkipped = signal(false);
6645
+ this.dateEdits = signal({});
6646
+ this.routeEdits = signal({});
6647
+ this.searchedAirports = signal([]);
6648
+ this.load$ = new Subject();
6649
+ this.airportTerm$ = new Subject();
6650
+ // ── derived ───────────────────────────────────────────────────────────────
6651
+ this.ctx = computed(() => {
6652
+ const b = this.booking();
6653
+ return b ? buildReissueBookingContext(b.data, b.id) : null;
6654
+ });
6655
+ this.legs = computed(() => this.ctx()?.legs ?? []);
6656
+ this.selectedSet = computed(() => new Set(this.selectedLegIds()));
6657
+ this.selectedLegs = computed(() => this.legs().filter(l => this.selectedSet().has(l.legId)));
6658
+ this.legRows = computed(() => this.legs().map(leg => buildLegRow(leg, this.availableIds().has(leg.legId), this.selectedSet().has(leg.legId))));
6659
+ /** `false` = a round trip must be upgraded as a whole (availability's `allowCustomerToSelectSingleLeg`). */
6660
+ this.allowSingleLegSelection = signal(true);
6661
+ this.anyAvailable = computed(() => this.legRows().some(r => r.available));
6662
+ /**
6663
+ * No leg can be reissued online (offline, or already flown): the dialog shows this message instead of the options,
6664
+ * like the cancel dialog does for a booking it cannot auto-cancel. `null` while loading or when any leg is available.
6665
+ */
6666
+ this.blocked = computed(() => {
6667
+ const rows = this.legRows();
6668
+ if (!this.availabilityLoaded() || !rows.length || rows.some(r => r.available))
6669
+ return null;
6670
+ return rows.every(r => r.flown)
6671
+ ? { title: 'All flights of this booking have already departed.', text: 'There is nothing left to reissue.' }
6672
+ : {
6673
+ title: 'This booking doesn’t support online reissue.',
6674
+ text: 'Please escalate to the relevant team for manual handling.',
6675
+ };
6676
+ });
6677
+ this.canContinue = computed(() => this.selectedLegIds().length > 0);
6678
+ /** A leg's date after the change: the new one when the agent is changing it, the original otherwise. */
6679
+ this.dateRows = computed(() => {
6680
+ const legs = this.legs();
6681
+ const selected = this.selectedSet();
6682
+ const edits = this.dateEdits();
6683
+ const effective = (l) => (selected.has(l.legId) ? (edits[l.legId] ?? '') : l.departure.slice(0, 10));
6684
+ return legs.flatMap((leg, i) => {
6685
+ if (!selected.has(leg.legId))
6686
+ return [];
6687
+ const prev = i > 0 ? effective(legs[i - 1]) : '';
6688
+ return [
6689
+ {
6690
+ leg,
6691
+ title: legTitle(leg),
6692
+ current: formatReissueDate(leg.departure),
6693
+ value: edits[leg.legId] ?? '',
6694
+ // Never before today or the previous leg; never after the next leg (same day is fine).
6695
+ min: prev > this.today ? prev : this.today,
6696
+ max: i < legs.length - 1 ? effective(legs[i + 1]) : '',
6697
+ },
6698
+ ];
6699
+ });
6700
+ });
6701
+ this.routeRows = computed(() => {
6702
+ const edits = this.routeEdits();
6703
+ return this.selectedLegs().map(leg => ({
6704
+ leg,
6705
+ title: legTitle(leg),
6706
+ current: formatReissueDate(leg.departure),
6707
+ origin: edits[leg.legId]?.origin ?? originAirport(leg),
6708
+ destination: edits[leg.legId]?.destination ?? destinationAirport(leg),
6709
+ }));
6710
+ });
6711
+ this.airportOptions = computed(() => {
6712
+ const byId = new Map();
6713
+ // Order matters (later wins): the booking's own airports, the search results, then what is already picked.
6714
+ for (const l of this.legs()) {
6715
+ byId.set(l.originCode, originAirport(l));
6716
+ byId.set(l.destinationCode, destinationAirport(l));
6717
+ }
6718
+ for (const a of this.searchedAirports())
6719
+ byId.set(a.id, a);
6720
+ for (const e of Object.values(this.routeEdits())) {
6721
+ byId.set(e.origin.id, e.origin);
6722
+ byId.set(e.destination.id, e.destination);
6723
+ }
6724
+ return [...byId.values()].map(a => ({ ...a, label: `${a.city_name} (${a.id}) – ${a.name}` }));
6725
+ });
6726
+ this.validationError = computed(() => {
6727
+ const service = this.service();
6728
+ if (!service || !this.selectedLegIds().length)
6729
+ return 'Select at least one leg.';
6730
+ if (service === 'change_date') {
6731
+ for (const { leg, value, min, max } of this.dateRows()) {
6732
+ const who = leg.direction || 'This leg';
6733
+ if (!value)
6734
+ return 'Pick a new date for every selected leg.';
6735
+ if (value < this.today)
6736
+ return 'The new date cannot be in the past.';
6737
+ if (value < min)
6738
+ return `${who} cannot depart before the previous flight (${min}).`;
6739
+ if (max && value > max)
6740
+ return `${who} cannot depart after the next flight (${max}).`;
6741
+ }
6742
+ return null;
6743
+ }
6744
+ let changed = false;
6745
+ for (const { leg, origin, destination } of this.routeRows()) {
6746
+ if (origin.id === destination.id)
6747
+ return 'Origin and destination must be different.';
6748
+ if (origin.id !== leg.originCode || destination.id !== leg.destinationCode)
6749
+ changed = true;
6750
+ }
6751
+ return changed ? null : 'Change at least one airport.';
6752
+ });
6753
+ this.load$.pipe(switchMap(() => this.loadFlow()), takeUntilDestroyed()).subscribe();
6754
+ // Debounced typeahead: one request per pause in typing, older in-flight ones are dropped.
6755
+ this.airportTerm$
6756
+ .pipe(debounceTime(250), distinctUntilChanged(), switchMap(term => this.api.searchAirports(term)), takeUntilDestroyed())
6757
+ .subscribe(list => this.searchedAirports.set(list));
6758
+ }
6759
+ /** The leg came back online from the availability check. */
6760
+ isLegOnline(legId) {
6761
+ return this.availableIds().has(legId);
6762
+ }
6763
+ // ── actions ───────────────────────────────────────────────────────────────
6764
+ /** (Re)starts the dialog for a booking: clears every choice, then loads post-booking + availability. */
6765
+ open(data, id) {
6766
+ this.booking.set({ data, id });
6767
+ this.step.set('service');
6768
+ this.service.set(null);
6769
+ this.selectedLegIds.set([]);
6770
+ this.dateEdits.set({});
6771
+ this.routeEdits.set({});
6772
+ this.searchedAirports.set([]);
6773
+ this.postBookingId.set('');
6774
+ this.availableIds.set(new Set());
6775
+ this.availabilityLoaded.set(false);
6776
+ this.upgradeOptions.set(null);
6777
+ this.load();
6778
+ }
6779
+ load() {
6780
+ this.load$.next();
6781
+ }
6782
+ chooseService(service) {
6783
+ if (this.loading() || this.error())
6784
+ return;
6785
+ this.service.set(service);
6786
+ // A one-way with its only leg online has nothing to choose: skip "select flight" and go straight to editing.
6787
+ // (An offline one-way still shows the step, so the agent sees why it cannot be changed.)
6788
+ const rows = this.legRows();
6789
+ if (rows.length === 1 && rows[0].available) {
6790
+ this.selectedLegIds.set([rows[0].leg.legId]);
6791
+ this.legsStepSkipped.set(true);
6792
+ this.goToEdit();
6793
+ return;
6794
+ }
6795
+ this.legsStepSkipped.set(false);
6796
+ this.selectedLegIds.set([]);
6797
+ this.step.set('legs');
6798
+ }
6799
+ toggleLeg(legId) {
6800
+ if (!this.availableIds().has(legId))
6801
+ return;
6802
+ this.selectedLegIds.update(ids => (ids.includes(legId) ? ids.filter(i => i !== legId) : [...ids, legId]));
6803
+ }
6804
+ /** Legs -> edit: seed each selected leg with its current date / airports. */
6805
+ goToEdit() {
6806
+ if (!this.canContinue())
6807
+ return;
6808
+ if (this.service() === 'change_date') {
6809
+ this.dateEdits.update(edits => {
6810
+ const next = { ...edits };
6811
+ for (const l of this.selectedLegs())
6812
+ next[l.legId] ??= l.departure.slice(0, 10);
6813
+ return next;
6814
+ });
6815
+ }
6816
+ else {
6817
+ this.routeEdits.update(edits => {
6818
+ const next = { ...edits };
6819
+ for (const l of this.selectedLegs()) {
6820
+ next[l.legId] ??= { origin: originAirport(l), destination: destinationAirport(l) };
6821
+ }
6822
+ return next;
6823
+ });
6824
+ }
6825
+ this.step.set('edit');
6826
+ }
6827
+ goTo(step) {
6828
+ this.step.set(step);
6829
+ }
6830
+ back() {
6831
+ const previous = {
6832
+ service: 'service',
6833
+ legs: 'service',
6834
+ edit: this.legsStepSkipped() ? 'service' : 'legs',
6835
+ 'upgrade-legs': 'service',
6836
+ upgrade: 'service', // the upgrade store overrides this when the leg step was shown
6837
+ confirm: 'upgrade',
6838
+ };
6839
+ this.step.update(s => previous[s]);
6840
+ }
6841
+ setDate(legId, date) {
6842
+ this.dateEdits.update(edits => ({ ...edits, [legId]: date }));
6843
+ }
6844
+ setAirport(legId, side, airport) {
6845
+ if (!airport)
6846
+ return;
6847
+ const current = this.routeRows().find(r => r.leg.legId === legId);
6848
+ if (!current)
6849
+ return;
6850
+ this.routeEdits.update(edits => ({
6851
+ ...edits,
6852
+ [legId]: { origin: current.origin, destination: current.destination, [side]: airport },
6853
+ }));
6854
+ }
6855
+ /** Swaps a leg's origin and destination (the arrow between the two pickers). */
6856
+ swapAirports(legId) {
6857
+ const current = this.routeRows().find(r => r.leg.legId === legId);
6858
+ if (!current)
6859
+ return;
6860
+ this.routeEdits.update(edits => ({
6861
+ ...edits,
6862
+ [legId]: { origin: current.destination, destination: current.origin },
6863
+ }));
6864
+ }
6865
+ searchAirports(term) {
6866
+ this.airportTerm$.next(term ?? '');
6867
+ }
6868
+ /** The web search URL for the current choices, or `null` when they are not valid yet. */
6869
+ searchUrl() {
6870
+ const ctx = this.ctx();
6871
+ const service = this.service();
6872
+ if (!ctx || !service || service === 'upgrade_class' || this.validationError())
6873
+ return null;
6874
+ const selection = {
6875
+ service,
6876
+ legs: service === 'change_date'
6877
+ ? this.dateRows().map(r => ({ legId: r.leg.legId, departDate: r.value }))
6878
+ : this.routeRows().map(r => ({ legId: r.leg.legId, origin: r.origin, destination: r.destination })),
6879
+ };
6880
+ return buildReissueSearchUrl(this.config.webBaseUrl, ctx, selection, this.postBookingId());
6881
+ }
6882
+ // ── loading: post-booking -> availability -> upgrade options (once per dialog visit) ─────────
6883
+ loadFlow() {
6884
+ const ctx = this.ctx();
6885
+ if (!ctx) {
6886
+ this.error.set('This booking has no leg data to reissue.');
6887
+ return EMPTY;
6888
+ }
6889
+ this.loading.set(true);
6890
+ this.error.set(null);
6891
+ return this.api.createPostBooking(ctx.bookingId).pipe(switchMap(postBookingId => {
6892
+ if (!postBookingId)
6893
+ return throwError(() => START_ERROR);
6894
+ this.postBookingId.set(postBookingId);
6895
+ return this.api.getAvailability(ctx.bookingId, postBookingId, ctx).pipe(catchError$1(e => throwError(() => describeHttpError(AVAILABILITY_ERROR, e))), tap(res => {
6896
+ // A leg that already flew can never be changed, whatever availability says: treat it as offline.
6897
+ const flown = new Set(ctx.legs.filter(isLegFlown).map(l => l.legId));
6898
+ this.availableIds.set(new Set((res?.data?.legs ?? []).filter(l => l.available && !flown.has(l.legId)).map(l => l.legId)));
6899
+ this.allowSingleLegSelection.set(res?.data?.allowCustomerToSelectSingleLeg ?? true);
6900
+ this.availabilityLoaded.set(true);
6901
+ }), switchMap(() => this.offersUpgrade(ctx.tripType, ctx.legs, postBookingId)));
6902
+ }), tap(options => {
6903
+ this.upgradeOptions.set(options);
6904
+ this.loading.set(false);
6905
+ }), catchError$1((e) => {
6906
+ this.error.set(typeof e === 'string' ? e : describeHttpError(START_ERROR, e));
6907
+ this.loading.set(false);
6908
+ return EMPTY;
6909
+ }));
6910
+ }
6911
+ /**
6912
+ * The upgrade-class options (same rule as the web for listing "Upgrade Class"): skipped for a one-way whose
6913
+ * only leg is offline; a failing call just yields `null` (option hidden) instead of blocking the dialog.
6914
+ */
6915
+ offersUpgrade(tripType, legs, postBookingId) {
6916
+ const ctx = this.ctx();
6917
+ const oneWayOffline = tripType === 'OneWay' && !this.availableIds().has(legs[0]?.legId);
6918
+ if (!ctx || !legs.length || oneWayOffline)
6919
+ return of(null);
6920
+ return this.api
6921
+ .getUpgradeClassOptions(ctx.bookingId, postBookingId, ctx)
6922
+ .pipe(catchError$1(() => of(null)));
6923
+ }
6924
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdReissueStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6925
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdReissueStore }); }
6926
+ }
6927
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdReissueStore, decorators: [{
6928
+ type: Injectable
6929
+ }], ctorParameters: () => [] });
6930
+
6931
+ const extraOf = (o) => o.fareDetails?.totalAdditionalFare ?? o.priceDifference ?? 0;
6932
+ const routeOf = (l) => `${l.origin} → ${l.destination}`;
6933
+ const currentOf = (l) => {
6934
+ if (!l)
6935
+ return '';
6936
+ const cabin = [l.class, l.fareBrand?.name].filter(Boolean).join(' · ');
6937
+ return cabin ? `Current: ${cabin}` : '';
6938
+ };
6939
+ /**
6940
+ * Upgrade Class flow: pick the new class -> price it (choose-flight) -> confirm (book-now when nothing to pay,
6941
+ * otherwise a backend-generated payment link). Reads the options the main store already loaded.
6942
+ */
6943
+ class SbdReissueUpgradeStore {
6944
+ constructor() {
6945
+ this.main = inject(SbdReissueStore);
6946
+ this.api = inject(SbdReissueService);
6947
+ this.destroyRef = inject(DestroyRef);
6948
+ this.submitting = signal(false);
6949
+ this.error = signal(null);
6950
+ this.priceChanged = signal(false);
6951
+ /** `book-now` succeeded (nothing to pay): the reissue is being placed with the supplier. */
6952
+ this.booked = signal(false);
6953
+ this.paymentLink = signal(null);
6954
+ this.selected = signal({}); // sectionId -> referenceKey
6955
+ this.unavailableKeys = signal(new Set());
6956
+ /** Legs the agent chose to upgrade (the "select leg" step; a one-way preselects its only leg). */
6957
+ this.legIds = signal([]);
6958
+ /** Options fetched for a partial leg selection; `null` = the options loaded when the dialog opened (all legs) apply. */
6959
+ this.freshOptions = signal(null);
6960
+ this.legsStepSkipped = signal(false);
6961
+ // ── derived ───────────────────────────────────────────────────────────────
6962
+ this.currency = computed(() => this.main.ctx()?.currency ?? '');
6963
+ /** The options to show: for a partial leg selection the fresh response, otherwise the one loaded with the dialog. */
6964
+ this.options = computed(() => this.freshOptions() ?? this.main.upgradeOptions());
6965
+ this.legRows = computed(() => {
6966
+ const chosen = new Set(this.legIds());
6967
+ return this.main
6968
+ .legs()
6969
+ .map((leg, i) => buildLegRow(leg, this.legUpgradable(leg.legId, i), chosen.has(leg.legId), this.main.isLegOnline(leg.legId) ? 'no-options' : 'offline'));
6970
+ });
6971
+ this.anyLegUpgradable = computed(() => this.legRows().some(r => r.available));
6972
+ this.canContinueFromLegs = computed(() => this.legIds().length > 0 && !this.submitting());
6973
+ /** Legs the options on screen were requested for (all of them, or just the selected ones after a partial fetch). */
6974
+ this.optionLegs = computed(() => {
6975
+ const all = this.main.legs();
6976
+ return this.freshOptions() ? all.filter(l => this.legIds().includes(l.legId)) : all;
6977
+ });
6978
+ this.rawSections = computed(() => {
6979
+ const data = this.options();
6980
+ if (!data)
6981
+ return [];
6982
+ if (data.fareOptionScope === 'leg') {
6983
+ return (data.legs ?? []).map(leg => ({
6984
+ id: String(leg.legIndex),
6985
+ title: routeOf({ origin: leg.origin ?? leg.existingLeg.origin, destination: leg.destination ?? leg.existingLeg.destination }),
6986
+ current: currentOf(leg.existingLeg),
6987
+ options: leg.options ?? [],
6988
+ }));
6989
+ }
6990
+ const originals = data.existingLegs ?? data.originalLegs ?? [];
6991
+ return [
6992
+ {
6993
+ id: 'booking',
6994
+ title: originals.map(routeOf).join(' · ') || 'Whole booking',
6995
+ current: currentOf(originals[0]),
6996
+ options: data.options ?? [],
6997
+ },
6998
+ ];
6999
+ });
7000
+ this.sections = computed(() => {
7001
+ const selected = this.selected();
7002
+ const unavailable = this.unavailableKeys();
7003
+ const currency = this.currency();
7004
+ return this.rawSections().map(s => ({
7005
+ id: s.id,
7006
+ title: s.title,
7007
+ current: s.current,
7008
+ options: s.options.map(o => {
7009
+ const extra = extraOf(o);
7010
+ return {
7011
+ key: o.referenceKey,
7012
+ name: o.name,
7013
+ code: o.code,
7014
+ extraLabel: extra > 0 ? `+${formatMoney(extra, currency)}` : 'No extra cost',
7015
+ selected: selected[s.id] === o.referenceKey,
7016
+ unavailable: unavailable.has(o.referenceKey),
7017
+ };
7018
+ }),
7019
+ }));
7020
+ });
7021
+ this.picks = computed(() => {
7022
+ const selected = this.selected();
7023
+ return this.rawSections().flatMap(section => {
7024
+ const option = section.options.find(o => o.referenceKey === selected[section.id]);
7025
+ return option ? [{ section, option }] : [];
7026
+ });
7027
+ });
7028
+ this.canContinue = computed(() => this.picks().length > 0 && !this.submitting());
7029
+ /**
7030
+ * Amount to pay: the sum of the picked options' additional fare (same as the web's `getTotalToPay`).
7031
+ * choose-flight's own `price` is deliberately not used — it is not known whether it is the difference or the full fare.
7032
+ */
7033
+ this.total = computed(() => this.picks().reduce((sum, p) => sum + extraOf(p.option), 0));
7034
+ this.totalLabel = computed(() => (this.total() > 0 ? formatMoney(this.total(), this.currency()) : 'Nothing to pay'));
7035
+ this.summary = computed(() => this.picks().map(({ section, option }) => ({
7036
+ title: section.title,
7037
+ from: section.current.replace(/^Current:\s*/, ''),
7038
+ to: option.name,
7039
+ extraLabel: extraOf(option) > 0 ? `+${formatMoney(extraOf(option), this.currency())}` : 'No extra cost',
7040
+ })));
7041
+ this.action = computed(() => {
7042
+ if (this.booked() || this.paymentLink())
7043
+ return 'done';
7044
+ return this.total() > 0 ? 'link' : 'book';
7045
+ });
7046
+ /** Nothing can be changed any more once the upgrade is placed or a link was generated. */
7047
+ this.locked = computed(() => this.booked() || !!this.paymentLink() || this.submitting());
7048
+ // A fresh options response (new dialog visit / retry) starts the flow over.
7049
+ effect(() => {
7050
+ this.main.upgradeOptions();
7051
+ untracked(() => this.reset());
7052
+ }, { allowSignalWrites: true });
7053
+ }
7054
+ // ── leg selection ─────────────────────────────────────────────────────────
7055
+ /** A leg can be upgraded only if it is online AND has upgrade options (per leg for leg scope; booking scope applies to all). */
7056
+ legUpgradable(legId, index) {
7057
+ if (!this.main.isLegOnline(legId))
7058
+ return false;
7059
+ const data = this.main.upgradeOptions();
7060
+ if (!data)
7061
+ return false;
7062
+ if (data.fareOptionScope === 'leg') {
7063
+ return (data.legs?.find(l => l.legIndex === index)?.options?.length ?? 0) > 0;
7064
+ }
7065
+ return (data.options?.length ?? 0) > 0;
7066
+ }
7067
+ // ── actions ───────────────────────────────────────────────────────────────
7068
+ /**
7069
+ * Service picked: go to "select leg", unless there is nothing to choose (one-way with its only leg upgradable),
7070
+ * which goes straight to the options like the web.
7071
+ */
7072
+ start() {
7073
+ if (this.main.loading() || this.main.error())
7074
+ return;
7075
+ this.main.service.set('upgrade_class');
7076
+ this.freshOptions.set(null);
7077
+ this.selected.set({});
7078
+ this.error.set(null);
7079
+ const upgradable = this.legRows().filter(r => r.available);
7080
+ if (this.legRows().length === 1 && upgradable.length === 1) {
7081
+ this.legIds.set([upgradable[0].leg.legId]);
7082
+ this.legsStepSkipped.set(true);
7083
+ this.main.goTo('upgrade');
7084
+ return;
7085
+ }
7086
+ this.legsStepSkipped.set(false);
7087
+ // "Upgrade as a whole" bookings: every upgradable leg is preselected and cannot be unticked.
7088
+ this.legIds.set(this.main.allowSingleLegSelection() ? [] : upgradable.map(r => r.leg.legId));
7089
+ this.main.goTo('upgrade-legs');
7090
+ }
7091
+ toggleLeg(legId) {
7092
+ const row = this.legRows().find(r => r.leg.legId === legId);
7093
+ if (!row?.available || !this.main.allowSingleLegSelection())
7094
+ return;
7095
+ this.error.set(null);
7096
+ this.legIds.update(ids => (ids.includes(legId) ? ids.filter(i => i !== legId) : [...ids, legId]));
7097
+ }
7098
+ /**
7099
+ * Select leg -> options. All legs selected: reuse the options loaded with the dialog. A subset: fetch the options
7100
+ * again for just those legs (the earlier response covers the whole booking, so it would price the wrong legs).
7101
+ */
7102
+ continueFromLegs() {
7103
+ const ctx = this.main.ctx();
7104
+ const chosen = this.main.legs().filter(l => this.legIds().includes(l.legId));
7105
+ if (!ctx || !chosen.length || !this.canContinueFromLegs())
7106
+ return;
7107
+ this.selected.set({});
7108
+ this.unavailableKeys.set(new Set());
7109
+ if (chosen.length === this.main.legs().length) {
7110
+ this.freshOptions.set(null);
7111
+ this.main.goTo('upgrade');
7112
+ return;
7113
+ }
7114
+ this.submitting.set(true);
7115
+ this.error.set(null);
7116
+ this.api
7117
+ .getUpgradeClassOptions(ctx.bookingId, this.main.postBookingId(), { ...ctx, legs: chosen })
7118
+ .pipe(finalize(() => this.submitting.set(false)), takeUntilDestroyed(this.destroyRef))
7119
+ .subscribe({
7120
+ next: data => {
7121
+ if (!this.api.hasUpgradeOptions(data)) {
7122
+ this.error.set('No upgrade is available for the selected flight(s).');
7123
+ return;
7124
+ }
7125
+ this.freshOptions.set(data);
7126
+ this.main.goTo('upgrade');
7127
+ },
7128
+ error: () => this.error.set('Could not load the upgrade options for the selected flight(s).'),
7129
+ });
7130
+ }
7131
+ /** Back from the options: to "select leg" when that step was shown, otherwise to the service list. */
7132
+ back() {
7133
+ this.main.goTo(this.legsStepSkipped() ? 'service' : 'upgrade-legs');
7134
+ }
7135
+ select(sectionId, key) {
7136
+ if (this.unavailableKeys().has(key))
7137
+ return;
7138
+ this.error.set(null);
7139
+ this.selected.update(s => ({ ...s, [sectionId]: key }));
7140
+ }
7141
+ /** Options -> confirm: prices the pick(s) with choose-flight (same body the web sends). */
7142
+ continue() {
7143
+ const ctx = this.main.ctx();
7144
+ const picks = this.picks();
7145
+ if (!ctx || !picks.length || !this.canContinue())
7146
+ return;
7147
+ this.submitting.set(true);
7148
+ this.error.set(null);
7149
+ this.api
7150
+ .chooseFlight(ctx.bookingId, this.main.postBookingId(), this.chooseBody(picks))
7151
+ .pipe(finalize(() => this.submitting.set(false)), takeUntilDestroyed(this.destroyRef))
7152
+ .subscribe({
7153
+ next: res => {
7154
+ if (res?.itineraryAvailable === false) {
7155
+ this.unavailableKeys.update(keys => new Set([...keys, ...picks.map(p => p.option.referenceKey)]));
7156
+ this.selected.set({});
7157
+ this.error.set('This option is no longer available. Please choose another one.');
7158
+ return;
7159
+ }
7160
+ this.priceChanged.set(res?.priceChanged === true);
7161
+ this.main.goTo('confirm');
7162
+ },
7163
+ error: () => this.error.set('Could not price the selected upgrade. Please try again.'),
7164
+ });
7165
+ }
7166
+ /** Confirm step primary action: place the free upgrade, or generate the payment link. */
7167
+ confirm() {
7168
+ const action = this.action();
7169
+ if (action === 'done' || this.submitting())
7170
+ return;
7171
+ const postBookingId = this.main.postBookingId();
7172
+ this.submitting.set(true);
7173
+ this.error.set(null);
7174
+ const request$ = action === 'book'
7175
+ ? this.api.bookNow(postBookingId)
7176
+ : this.api.getPaymentLink(postBookingId, this.total(), this.currency());
7177
+ request$
7178
+ .pipe(finalize(() => this.submitting.set(false)), takeUntilDestroyed(this.destroyRef))
7179
+ .subscribe({
7180
+ next: res => {
7181
+ // The dialog does not follow the result (no confirmation-status): after `book-now`, or once the link is
7182
+ // sent, the agent follows the booking from the dashboard.
7183
+ if (action === 'book')
7184
+ this.booked.set(true);
7185
+ else
7186
+ this.paymentLink.set(res);
7187
+ },
7188
+ error: (err) => this.error.set(action === 'book'
7189
+ ? 'Could not place the upgrade. Please try again.'
7190
+ : err instanceof Error && err.message.startsWith('The cart ')
7191
+ ? err.message
7192
+ : 'Could not get the payment link. Please try again.'),
7193
+ });
7194
+ }
7195
+ reset() {
7196
+ this.selected.set({});
7197
+ this.unavailableKeys.set(new Set());
7198
+ this.legIds.set([]);
7199
+ this.freshOptions.set(null);
7200
+ this.legsStepSkipped.set(false);
7201
+ this.submitting.set(false);
7202
+ this.error.set(null);
7203
+ this.priceChanged.set(false);
7204
+ this.booked.set(false);
7205
+ this.paymentLink.set(null);
7206
+ }
7207
+ /** Booking scope: one itinerary. Leg scope: each picked leg as onwardLeg / returnLeg (by the real leg direction). */
7208
+ chooseBody(picks) {
7209
+ const scope = this.options()?.fareOptionScope ?? 'booking';
7210
+ if (scope !== 'leg')
7211
+ return { fareOptionScope: 'booking', ItineraryId: picks[0].option.referenceKey };
7212
+ // A partial fetch re-indexes the sections from 0, so resolve the direction from the legs the options were requested for.
7213
+ const legs = this.optionLegs();
7214
+ const toLeg = (p) => (p ? { legId: p.option.referenceKey, providerType: p.option.providerType } : undefined);
7215
+ const isReturn = (p) => legs[Number(p.section.id)]?.direction === 'Return';
7216
+ return {
7217
+ fareOptionScope: 'leg',
7218
+ onwardLeg: toLeg(picks.find(p => !isReturn(p))),
7219
+ returnLeg: toLeg(picks.find(isReturn)),
7220
+ };
7221
+ }
7222
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdReissueUpgradeStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
7223
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdReissueUpgradeStore }); }
7224
+ }
7225
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdReissueUpgradeStore, decorators: [{
7226
+ type: Injectable
7227
+ }], ctorParameters: () => [] });
7228
+
7229
+ /** Icon + message box used for the info callout, loading / error states and the validation hint. */
7230
+ class SbdReissueNoticeComponent {
7231
+ constructor() {
7232
+ this.icon = input('pi pi-info-circle');
7233
+ this.tone = input('info');
7234
+ }
7235
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdReissueNoticeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7236
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.2.14", type: SbdReissueNoticeComponent, isStandalone: true, selector: "app-sbd-reissue-notice", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "\"sbd-notice sbd-notice--\" + tone()" } }, ngImport: i0, template: `<i [class]="icon()"></i><span class="sbd-notice__text"><ng-content /></span><ng-content select="[action]" />`, isInline: true, styles: [":host{display:flex;align-items:flex-start;gap:10px;padding:12px 14px;border-radius:12px;font-size:13px;line-height:1.5}:host i{flex-shrink:0;margin-top:1px}:host .sbd-notice__text{flex:1}:host(.sbd-notice--info){background:#eaf2ff;border:1px solid #b9d6ff;color:#1a5fd6}:host(.sbd-notice--warn){background:#fdf3e3;border:1px solid #f3d9a4;color:#8a5a1e}:host(.sbd-notice--warn) i{color:#b98a3e}:host(.sbd-notice--error){background:#fef2f2;border:1px solid #fecaca;color:#b91c1c}:host(.sbd-notice--success){background:#f0fdf4;border:1px solid #bbf7d0;color:#166534}:host(.sbd-notice--muted){align-items:center;color:#64748b}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7237
+ }
7238
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdReissueNoticeComponent, decorators: [{
7239
+ type: Component,
7240
+ args: [{ selector: 'app-sbd-reissue-notice', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: { '[class]': '"sbd-notice sbd-notice--" + tone()' }, template: `<i [class]="icon()"></i><span class="sbd-notice__text"><ng-content /></span><ng-content select="[action]" />`, styles: [":host{display:flex;align-items:flex-start;gap:10px;padding:12px 14px;border-radius:12px;font-size:13px;line-height:1.5}:host i{flex-shrink:0;margin-top:1px}:host .sbd-notice__text{flex:1}:host(.sbd-notice--info){background:#eaf2ff;border:1px solid #b9d6ff;color:#1a5fd6}:host(.sbd-notice--warn){background:#fdf3e3;border:1px solid #f3d9a4;color:#8a5a1e}:host(.sbd-notice--warn) i{color:#b98a3e}:host(.sbd-notice--error){background:#fef2f2;border:1px solid #fecaca;color:#b91c1c}:host(.sbd-notice--success){background:#f0fdf4;border:1px solid #bbf7d0;color:#166534}:host(.sbd-notice--muted){align-items:center;color:#64748b}\n"] }]
7241
+ }] });
7242
+
7243
+ /** New-class options as radio cards, in the look of the cancel dialog's refund-method options. */
7244
+ class SbdReissueUpgradeStepComponent {
7245
+ constructor() {
7246
+ this.sections = input.required();
7247
+ this.error = input(null);
7248
+ this.picked = output();
7249
+ }
7250
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdReissueUpgradeStepComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7251
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SbdReissueUpgradeStepComponent, isStandalone: true, selector: "app-sbd-reissue-upgrade-step", inputs: { sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: true, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { picked: "picked" }, host: { classAttribute: "sbd-rup" }, ngImport: i0, template: `
7252
+ @if (error()) {
7253
+ <app-sbd-reissue-notice tone="error" icon="pi pi-exclamation-circle">{{ error() }}</app-sbd-reissue-notice>
7254
+ }
7255
+
7256
+ @for (section of sections(); track section.id) {
7257
+ <section class="sbd-rup__section">
7258
+ <div class="sbd-rup__head">
7259
+ <span class="sbd-rup__route">{{ section.title }}</span>
7260
+ @if (section.current) { <span class="sbd-rup__current">{{ section.current }}</span> }
7261
+ </div>
7262
+
7263
+ @if (!section.options.length) {
7264
+ <span class="sbd-rup__empty">No upgrade available for this flight.</span>
7265
+ }
7266
+
7267
+ @for (option of section.options; track option.key) {
7268
+ <button type="button" class="sbd-rup__option"
7269
+ [class.sbd-rup__option--active]="option.selected"
7270
+ [disabled]="option.unavailable"
7271
+ (click)="picked.emit({ sectionId: section.id, key: option.key })">
7272
+ <span class="sbd-rup__radio" [class.sbd-rup__radio--on]="option.selected"></span>
7273
+ <span class="sbd-rup__text">
7274
+ <strong>{{ option.name }}</strong>
7275
+ <small>{{ option.code }}</small>
7276
+ </span>
7277
+ @if (option.unavailable) {
7278
+ <span class="sbd-rup__unavailable">Not available</span>
7279
+ } @else {
7280
+ <span class="sbd-rup__price">{{ option.extraLabel }}</span>
7281
+ }
7282
+ </button>
7283
+ }
7284
+ </section>
7285
+ }
7286
+ `, isInline: true, styles: [":host{display:flex;flex-direction:column;gap:14px}.sbd-rup__section{display:flex;flex-direction:column;gap:10px}.sbd-rup__head{display:flex;flex-direction:column;gap:2px}.sbd-rup__route{font-size:15px;font-weight:700;color:#1e2e4a}.sbd-rup__current,.sbd-rup__empty{font-size:13px;color:#64748b}.sbd-rup__option{border:1.5px solid #e2e8f0;border-radius:16px;background:#fff;transition:border-color .15s,box-shadow .15s;display:flex;align-items:center;gap:12px;width:100%;padding:16px;text-align:left;font-family:inherit;cursor:pointer}.sbd-rup__option--active{border-color:var(--sbd-accent, #6d4aff);box-shadow:0 0 0 1px var(--sbd-accent, #6d4aff)}.sbd-rup__option:disabled{opacity:.5;cursor:not-allowed}.sbd-rup__option:hover:not(:disabled):not(.sbd-rup__option--active){border-color:#cbd5e1}.sbd-rup__radio{flex-shrink:0;width:20px;height:20px;border-radius:50%;border:2px solid #cbd5e1;background:#fff}.sbd-rup__radio--on{border-color:var(--sbd-accent, #6d4aff);box-shadow:inset 0 0 0 4px #fff;background:var(--sbd-accent, #6d4aff)}.sbd-rup__text{flex:1;display:flex;flex-direction:column;gap:2px}.sbd-rup__text strong{font-size:14px;font-weight:700;color:#1e2e4a}.sbd-rup__text small{font-size:12.5px;color:#64748b}.sbd-rup__price{font-size:14px;font-weight:800;color:#1e2e4a;white-space:nowrap;font-variant-numeric:tabular-nums}.sbd-rup__unavailable{font-size:11.5px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:#64748b;background:#f8fafc;box-shadow:inset 0 0 0 1px #e2e8f0;border-radius:20px;padding:3px 12px;white-space:nowrap}\n"], dependencies: [{ kind: "component", type: SbdReissueNoticeComponent, selector: "app-sbd-reissue-notice", inputs: ["icon", "tone"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7287
+ }
7288
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdReissueUpgradeStepComponent, decorators: [{
7289
+ type: Component,
7290
+ args: [{ selector: 'app-sbd-reissue-upgrade-step', standalone: true, imports: [SbdReissueNoticeComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
7291
+ @if (error()) {
7292
+ <app-sbd-reissue-notice tone="error" icon="pi pi-exclamation-circle">{{ error() }}</app-sbd-reissue-notice>
7293
+ }
7294
+
7295
+ @for (section of sections(); track section.id) {
7296
+ <section class="sbd-rup__section">
7297
+ <div class="sbd-rup__head">
7298
+ <span class="sbd-rup__route">{{ section.title }}</span>
7299
+ @if (section.current) { <span class="sbd-rup__current">{{ section.current }}</span> }
7300
+ </div>
7301
+
7302
+ @if (!section.options.length) {
7303
+ <span class="sbd-rup__empty">No upgrade available for this flight.</span>
7304
+ }
7305
+
7306
+ @for (option of section.options; track option.key) {
7307
+ <button type="button" class="sbd-rup__option"
7308
+ [class.sbd-rup__option--active]="option.selected"
7309
+ [disabled]="option.unavailable"
7310
+ (click)="picked.emit({ sectionId: section.id, key: option.key })">
7311
+ <span class="sbd-rup__radio" [class.sbd-rup__radio--on]="option.selected"></span>
7312
+ <span class="sbd-rup__text">
7313
+ <strong>{{ option.name }}</strong>
7314
+ <small>{{ option.code }}</small>
7315
+ </span>
7316
+ @if (option.unavailable) {
7317
+ <span class="sbd-rup__unavailable">Not available</span>
7318
+ } @else {
7319
+ <span class="sbd-rup__price">{{ option.extraLabel }}</span>
7320
+ }
7321
+ </button>
7322
+ }
7323
+ </section>
7324
+ }
7325
+ `, host: { class: 'sbd-rup' }, styles: [":host{display:flex;flex-direction:column;gap:14px}.sbd-rup__section{display:flex;flex-direction:column;gap:10px}.sbd-rup__head{display:flex;flex-direction:column;gap:2px}.sbd-rup__route{font-size:15px;font-weight:700;color:#1e2e4a}.sbd-rup__current,.sbd-rup__empty{font-size:13px;color:#64748b}.sbd-rup__option{border:1.5px solid #e2e8f0;border-radius:16px;background:#fff;transition:border-color .15s,box-shadow .15s;display:flex;align-items:center;gap:12px;width:100%;padding:16px;text-align:left;font-family:inherit;cursor:pointer}.sbd-rup__option--active{border-color:var(--sbd-accent, #6d4aff);box-shadow:0 0 0 1px var(--sbd-accent, #6d4aff)}.sbd-rup__option:disabled{opacity:.5;cursor:not-allowed}.sbd-rup__option:hover:not(:disabled):not(.sbd-rup__option--active){border-color:#cbd5e1}.sbd-rup__radio{flex-shrink:0;width:20px;height:20px;border-radius:50%;border:2px solid #cbd5e1;background:#fff}.sbd-rup__radio--on{border-color:var(--sbd-accent, #6d4aff);box-shadow:inset 0 0 0 4px #fff;background:var(--sbd-accent, #6d4aff)}.sbd-rup__text{flex:1;display:flex;flex-direction:column;gap:2px}.sbd-rup__text strong{font-size:14px;font-weight:700;color:#1e2e4a}.sbd-rup__text small{font-size:12.5px;color:#64748b}.sbd-rup__price{font-size:14px;font-weight:800;color:#1e2e4a;white-space:nowrap;font-variant-numeric:tabular-nums}.sbd-rup__unavailable{font-size:11.5px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:#64748b;background:#f8fafc;box-shadow:inset 0 0 0 1px #e2e8f0;border-radius:20px;padding:3px 12px;white-space:nowrap}\n"] }]
7326
+ }] });
7327
+
7328
+ class SbdReissueUpgradeConfirmStepComponent {
7329
+ constructor() {
7330
+ this.rows = input.required();
7331
+ this.total = input(0);
7332
+ this.totalLabel = input('');
7333
+ this.priceChanged = input(false);
7334
+ /** `book-now` was sent. */
7335
+ this.booked = input(false);
7336
+ this.link = input(null);
7337
+ this.error = input(null);
7338
+ /** The link expired and the agent chose to redo the upgrade from the beginning. */
7339
+ this.restart = output();
7340
+ this.copied = signal(false);
7341
+ /** Ticks every second so the countdown (and the expired state) update on their own. */
7342
+ this.now = signal(Date.now());
7343
+ this.remainingMs = computed(() => {
7344
+ const expiry = parseLinkExpiry(this.link()?.expiresAt);
7345
+ return expiry == null ? null : expiry - this.now();
7346
+ });
7347
+ this.expired = computed(() => {
7348
+ const left = this.remainingMs();
7349
+ return left != null && left <= 0;
7350
+ });
7351
+ /** `mm:ss` until the link expires; empty when the backend sent no (valid) expiry. */
7352
+ this.countdown = computed(() => {
7353
+ const left = this.remainingMs();
7354
+ return left == null || left <= 0 ? '' : formatCountdown(left);
7355
+ });
7356
+ /** Last two minutes: highlight the countdown. */
7357
+ this.soon = computed(() => (this.remainingMs() ?? Infinity) <= 2 * 60 * 1000);
7358
+ const timer = setInterval(() => this.now.set(Date.now()), 1000);
7359
+ inject(DestroyRef).onDestroy(() => clearInterval(timer));
7360
+ }
7361
+ async copy(url) {
7362
+ try {
7363
+ await navigator.clipboard.writeText(url);
7364
+ this.copied.set(true);
7365
+ setTimeout(() => this.copied.set(false), 1500);
7366
+ }
7367
+ catch {
7368
+ // Clipboard blocked (e.g. insecure context): the input is selectable, so the agent can still copy by hand.
7369
+ }
7370
+ }
7371
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdReissueUpgradeConfirmStepComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7372
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SbdReissueUpgradeConfirmStepComponent, isStandalone: true, selector: "app-sbd-reissue-upgrade-confirm-step", inputs: { rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, total: { classPropertyName: "total", publicName: "total", isSignal: true, isRequired: false, transformFunction: null }, totalLabel: { classPropertyName: "totalLabel", publicName: "totalLabel", isSignal: true, isRequired: false, transformFunction: null }, priceChanged: { classPropertyName: "priceChanged", publicName: "priceChanged", isSignal: true, isRequired: false, transformFunction: null }, booked: { classPropertyName: "booked", publicName: "booked", isSignal: true, isRequired: false, transformFunction: null }, link: { classPropertyName: "link", publicName: "link", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { restart: "restart" }, host: { classAttribute: "sbd-rconf" }, ngImport: i0, template: `
7373
+ @if (priceChanged()) {
7374
+ <app-sbd-reissue-notice tone="warn" icon="pi pi-exclamation-triangle">
7375
+ The price changed since the options were loaded. Check the total below.
7376
+ </app-sbd-reissue-notice>
7377
+ }
7378
+ @if (error()) {
7379
+ <app-sbd-reissue-notice tone="error" icon="pi pi-exclamation-circle">{{ error() }}</app-sbd-reissue-notice>
7380
+ }
7381
+
7382
+ <div class="sbd-rconf__summary">
7383
+ @for (row of rows(); track row.title) {
7384
+ <div class="sbd-rconf__row">
7385
+ <span class="sbd-rconf__row-main">
7386
+ <b>{{ row.title }}</b>
7387
+ <small>{{ row.from ? row.from + ' → ' : '' }}{{ row.to }}</small>
7388
+ </span>
7389
+ <strong>{{ row.extraLabel }}</strong>
7390
+ </div>
7391
+ }
7392
+ <div class="sbd-rconf__total">
7393
+ <span>Total to pay</span>
7394
+ <strong>{{ totalLabel() }}</strong>
7395
+ </div>
7396
+ </div>
7397
+
7398
+ @if (link(); as link) {
7399
+ @if (expired()) {
7400
+ <app-sbd-reissue-notice tone="error" icon="pi pi-clock">
7401
+ The payment link expired. Start the upgrade again from the beginning.
7402
+ <button action pButton type="button" class="p-button-sm p-button-outlined p-button-secondary" label="Start again"
7403
+ (click)="restart.emit()"></button>
7404
+ </app-sbd-reissue-notice>
7405
+ } @else {
7406
+ <div class="sbd-rconf__link">
7407
+ <div class="sbd-rconf__link-head"><b>Payment link</b><small>Send it to the customer</small></div>
7408
+ <div class="sbd-rconf__link-row">
7409
+ <input class="sbd-rconf__link-input" readonly [value]="link.url" (focus)="$any($event.target).select()" />
7410
+ <button pButton type="button" class="p-button-sm p-button-outlined p-button-secondary"
7411
+ [icon]="copied() ? 'pi pi-check' : 'pi pi-copy'" [label]="copied() ? 'Copied' : 'Copy'" (click)="copy(link.url)"></button>
7412
+ </div>
7413
+ @if (countdown(); as left) {
7414
+ <small class="sbd-rconf__expiry" [class.sbd-rconf__expiry--soon]="soon()">Link expires in {{ left }}</small>
7415
+ } @else if (link.expiresAt) {
7416
+ <small class="sbd-rconf__expiry">Expires {{ link.expiresAt.slice(0, 16).replace('T', ' ') }}</small>
7417
+ }
7418
+ </div>
7419
+ }
7420
+ }
7421
+
7422
+ <!-- The dialog does not follow the result: after book-now, or once the link is sent, the agent follows the booking. -->
7423
+ @if (booked()) {
7424
+ <app-sbd-reissue-notice tone="success" icon="pi pi-check-circle">
7425
+ The upgrade request was sent. Check the booking for the result.
7426
+ </app-sbd-reissue-notice>
7427
+ } @else if (link()) {
7428
+ @if (!expired()) {
7429
+ <app-sbd-reissue-notice tone="success" icon="pi pi-check-circle">
7430
+ Copy the link and send it to the customer. The upgrade is placed once they pay: check the booking then.
7431
+ </app-sbd-reissue-notice>
7432
+ }
7433
+ } @else {
7434
+ <app-sbd-reissue-notice icon="pi pi-info-circle">
7435
+ {{ total() > 0
7436
+ ? 'A payment link will be generated for the customer to pay the difference.'
7437
+ : 'Nothing to pay: confirming places the upgrade straight away.' }}
7438
+ </app-sbd-reissue-notice>
7439
+ }
7440
+ `, isInline: true, styles: [":host{display:flex;flex-direction:column;gap:14px}.sbd-rconf__summary{background:#f8fafc;border:1px solid #e2e8f0;border-radius:12px;padding:14px 16px;display:flex;flex-direction:column;gap:12px}.sbd-rconf__row{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:13.5px}.sbd-rconf__row strong{font-size:14px;font-weight:800;color:#1e2e4a;font-variant-numeric:tabular-nums;white-space:nowrap}.sbd-rconf__row-main{display:flex;flex-direction:column;gap:2px;min-width:0}.sbd-rconf__row-main b{font-weight:700;color:#1e2e4a}.sbd-rconf__row-main small{font-size:12.5px;color:#64748b}.sbd-rconf__total{display:flex;align-items:center;justify-content:space-between;border-top:1px solid #e2e8f0;padding-top:12px}.sbd-rconf__total span{font-size:14.5px;font-weight:700;color:#1e2e4a}.sbd-rconf__total strong{font-size:17px;font-weight:800;color:#1e2e4a;font-variant-numeric:tabular-nums}.sbd-rconf__link{border:1.5px solid #e2e8f0;border-radius:16px;background:#fff;transition:border-color .15s,box-shadow .15s;padding:16px;display:flex;flex-direction:column;gap:10px}.sbd-rconf__link-head{display:flex;justify-content:space-between;align-items:center;font-size:14px;color:#1e2e4a}.sbd-rconf__link-head small{color:#94a3b8;font-size:12.5px}.sbd-rconf__link-row{display:flex;gap:8px}.sbd-rconf__link-input{height:40px;padding:0 12px;border:1.5px solid #e2e8f0;border-radius:10px;background:#fff;font-size:13px;font-family:inherit;color:#1e2e4a}.sbd-rconf__link-input:focus{outline:none;border-color:var(--sbd-accent, #6d4aff)}.sbd-rconf__link-input{flex:1;min-width:0;height:36px;font-size:12px;color:#334155}.sbd-rconf__expiry{color:#94a3b8;font-size:12.5px;font-variant-numeric:tabular-nums}.sbd-rconf__expiry--soon{color:#b45309;font-weight:600}\n"], dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i2$1.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "label", "icon", "loading"] }, { kind: "component", type: SbdReissueNoticeComponent, selector: "app-sbd-reissue-notice", inputs: ["icon", "tone"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7441
+ }
7442
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdReissueUpgradeConfirmStepComponent, decorators: [{
7443
+ type: Component,
7444
+ args: [{ selector: 'app-sbd-reissue-upgrade-confirm-step', standalone: true, imports: [ButtonModule, SbdReissueNoticeComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
7445
+ @if (priceChanged()) {
7446
+ <app-sbd-reissue-notice tone="warn" icon="pi pi-exclamation-triangle">
7447
+ The price changed since the options were loaded. Check the total below.
7448
+ </app-sbd-reissue-notice>
7449
+ }
7450
+ @if (error()) {
7451
+ <app-sbd-reissue-notice tone="error" icon="pi pi-exclamation-circle">{{ error() }}</app-sbd-reissue-notice>
7452
+ }
7453
+
7454
+ <div class="sbd-rconf__summary">
7455
+ @for (row of rows(); track row.title) {
7456
+ <div class="sbd-rconf__row">
7457
+ <span class="sbd-rconf__row-main">
7458
+ <b>{{ row.title }}</b>
7459
+ <small>{{ row.from ? row.from + ' → ' : '' }}{{ row.to }}</small>
7460
+ </span>
7461
+ <strong>{{ row.extraLabel }}</strong>
7462
+ </div>
7463
+ }
7464
+ <div class="sbd-rconf__total">
7465
+ <span>Total to pay</span>
7466
+ <strong>{{ totalLabel() }}</strong>
7467
+ </div>
7468
+ </div>
7469
+
7470
+ @if (link(); as link) {
7471
+ @if (expired()) {
7472
+ <app-sbd-reissue-notice tone="error" icon="pi pi-clock">
7473
+ The payment link expired. Start the upgrade again from the beginning.
7474
+ <button action pButton type="button" class="p-button-sm p-button-outlined p-button-secondary" label="Start again"
7475
+ (click)="restart.emit()"></button>
7476
+ </app-sbd-reissue-notice>
7477
+ } @else {
7478
+ <div class="sbd-rconf__link">
7479
+ <div class="sbd-rconf__link-head"><b>Payment link</b><small>Send it to the customer</small></div>
7480
+ <div class="sbd-rconf__link-row">
7481
+ <input class="sbd-rconf__link-input" readonly [value]="link.url" (focus)="$any($event.target).select()" />
7482
+ <button pButton type="button" class="p-button-sm p-button-outlined p-button-secondary"
7483
+ [icon]="copied() ? 'pi pi-check' : 'pi pi-copy'" [label]="copied() ? 'Copied' : 'Copy'" (click)="copy(link.url)"></button>
7484
+ </div>
7485
+ @if (countdown(); as left) {
7486
+ <small class="sbd-rconf__expiry" [class.sbd-rconf__expiry--soon]="soon()">Link expires in {{ left }}</small>
7487
+ } @else if (link.expiresAt) {
7488
+ <small class="sbd-rconf__expiry">Expires {{ link.expiresAt.slice(0, 16).replace('T', ' ') }}</small>
7489
+ }
7490
+ </div>
7491
+ }
7492
+ }
7493
+
7494
+ <!-- The dialog does not follow the result: after book-now, or once the link is sent, the agent follows the booking. -->
7495
+ @if (booked()) {
7496
+ <app-sbd-reissue-notice tone="success" icon="pi pi-check-circle">
7497
+ The upgrade request was sent. Check the booking for the result.
7498
+ </app-sbd-reissue-notice>
7499
+ } @else if (link()) {
7500
+ @if (!expired()) {
7501
+ <app-sbd-reissue-notice tone="success" icon="pi pi-check-circle">
7502
+ Copy the link and send it to the customer. The upgrade is placed once they pay: check the booking then.
7503
+ </app-sbd-reissue-notice>
7504
+ }
7505
+ } @else {
7506
+ <app-sbd-reissue-notice icon="pi pi-info-circle">
7507
+ {{ total() > 0
7508
+ ? 'A payment link will be generated for the customer to pay the difference.'
7509
+ : 'Nothing to pay: confirming places the upgrade straight away.' }}
7510
+ </app-sbd-reissue-notice>
7511
+ }
7512
+ `, host: { class: 'sbd-rconf' }, styles: [":host{display:flex;flex-direction:column;gap:14px}.sbd-rconf__summary{background:#f8fafc;border:1px solid #e2e8f0;border-radius:12px;padding:14px 16px;display:flex;flex-direction:column;gap:12px}.sbd-rconf__row{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:13.5px}.sbd-rconf__row strong{font-size:14px;font-weight:800;color:#1e2e4a;font-variant-numeric:tabular-nums;white-space:nowrap}.sbd-rconf__row-main{display:flex;flex-direction:column;gap:2px;min-width:0}.sbd-rconf__row-main b{font-weight:700;color:#1e2e4a}.sbd-rconf__row-main small{font-size:12.5px;color:#64748b}.sbd-rconf__total{display:flex;align-items:center;justify-content:space-between;border-top:1px solid #e2e8f0;padding-top:12px}.sbd-rconf__total span{font-size:14.5px;font-weight:700;color:#1e2e4a}.sbd-rconf__total strong{font-size:17px;font-weight:800;color:#1e2e4a;font-variant-numeric:tabular-nums}.sbd-rconf__link{border:1.5px solid #e2e8f0;border-radius:16px;background:#fff;transition:border-color .15s,box-shadow .15s;padding:16px;display:flex;flex-direction:column;gap:10px}.sbd-rconf__link-head{display:flex;justify-content:space-between;align-items:center;font-size:14px;color:#1e2e4a}.sbd-rconf__link-head small{color:#94a3b8;font-size:12.5px}.sbd-rconf__link-row{display:flex;gap:8px}.sbd-rconf__link-input{height:40px;padding:0 12px;border:1.5px solid #e2e8f0;border-radius:10px;background:#fff;font-size:13px;font-family:inherit;color:#1e2e4a}.sbd-rconf__link-input:focus{outline:none;border-color:var(--sbd-accent, #6d4aff)}.sbd-rconf__link-input{flex:1;min-width:0;height:36px;font-size:12px;color:#334155}.sbd-rconf__expiry{color:#94a3b8;font-size:12.5px;font-variant-numeric:tabular-nums}.sbd-rconf__expiry--soon{color:#b45309;font-weight:600}\n"] }]
7513
+ }], ctorParameters: () => [] });
7514
+
7515
+ const OPTIONS = [
7516
+ { key: 'change_date', icon: 'pi pi-calendar', title: 'Change Flight Date', description: 'Move one or more flights to a new date' },
7517
+ { key: 'change_route', icon: 'pi pi-map', title: 'Change Route', description: 'Change the origin or destination airport' },
7518
+ { key: 'upgrade_class', icon: 'pi pi-arrow-up', title: 'Upgrade Class', description: 'Move to a higher cabin or fare class' },
7519
+ ];
7520
+ class SbdReissueServiceStepComponent {
7521
+ constructor() {
7522
+ this.upgradeAvailable = input(false);
7523
+ this.chosen = output();
7524
+ /** Upgrade Class is listed only when the upgrade-class API returned options. */
7525
+ this.options = computed(() => OPTIONS.filter(o => o.key !== 'upgrade_class' || this.upgradeAvailable()));
7526
+ }
7527
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdReissueServiceStepComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7528
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SbdReissueServiceStepComponent, isStandalone: true, selector: "app-sbd-reissue-service-step", inputs: { upgradeAvailable: { classPropertyName: "upgradeAvailable", publicName: "upgradeAvailable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { chosen: "chosen" }, host: { classAttribute: "sbd-rsvc" }, ngImport: i0, template: `
7529
+ @for (option of options(); track option.key) {
7530
+ <button type="button" class="sbd-rsvc__option" (click)="chosen.emit(option.key)">
7531
+ <span class="sbd-rsvc__icon"><i [class]="option.icon"></i></span>
7532
+ <span class="sbd-rsvc__text">
7533
+ <strong>{{ option.title }}</strong>
7534
+ <small>{{ option.description }}</small>
7535
+ </span>
7536
+ <i class="pi pi-chevron-right sbd-rsvc__arrow"></i>
7537
+ </button>
7538
+ }
7539
+ `, isInline: true, styles: [":host{display:flex;flex-direction:column;gap:12px}.sbd-rsvc__option{border:1.5px solid #e2e8f0;border-radius:16px;background:#fff;transition:border-color .15s,box-shadow .15s;display:flex;align-items:center;gap:12px;width:100%;padding:16px;text-align:left;font-family:inherit;cursor:pointer}.sbd-rsvc__option:hover{border-color:var(--sbd-accent, #6d4aff)}.sbd-rsvc__icon{flex-shrink:0;width:44px;height:44px;border-radius:10px;border:1px solid #e2e8f0;color:#1e2e4a;display:flex;align-items:center;justify-content:center}.sbd-rsvc__icon .pi{font-size:16px}.sbd-rsvc__text{flex:1;display:flex;flex-direction:column;gap:2px}.sbd-rsvc__text strong{font-size:15px;font-weight:700;color:#1e2e4a}.sbd-rsvc__text small{font-size:12.5px;color:#64748b}.sbd-rsvc__arrow{color:#94a3b8;font-size:12px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7540
+ }
7541
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdReissueServiceStepComponent, decorators: [{
7542
+ type: Component,
7543
+ args: [{ selector: 'app-sbd-reissue-service-step', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
7544
+ @for (option of options(); track option.key) {
7545
+ <button type="button" class="sbd-rsvc__option" (click)="chosen.emit(option.key)">
7546
+ <span class="sbd-rsvc__icon"><i [class]="option.icon"></i></span>
7547
+ <span class="sbd-rsvc__text">
7548
+ <strong>{{ option.title }}</strong>
7549
+ <small>{{ option.description }}</small>
7550
+ </span>
7551
+ <i class="pi pi-chevron-right sbd-rsvc__arrow"></i>
7552
+ </button>
7553
+ }
7554
+ `, host: { class: 'sbd-rsvc' }, styles: [":host{display:flex;flex-direction:column;gap:12px}.sbd-rsvc__option{border:1.5px solid #e2e8f0;border-radius:16px;background:#fff;transition:border-color .15s,box-shadow .15s;display:flex;align-items:center;gap:12px;width:100%;padding:16px;text-align:left;font-family:inherit;cursor:pointer}.sbd-rsvc__option:hover{border-color:var(--sbd-accent, #6d4aff)}.sbd-rsvc__icon{flex-shrink:0;width:44px;height:44px;border-radius:10px;border:1px solid #e2e8f0;color:#1e2e4a;display:flex;align-items:center;justify-content:center}.sbd-rsvc__icon .pi{font-size:16px}.sbd-rsvc__text{flex:1;display:flex;flex-direction:column;gap:2px}.sbd-rsvc__text strong{font-size:15px;font-weight:700;color:#1e2e4a}.sbd-rsvc__text small{font-size:12.5px;color:#64748b}.sbd-rsvc__arrow{color:#94a3b8;font-size:12px}\n"] }]
7555
+ }] });
7556
+
7557
+ /** Leg cards in the cancel dialog's "Select Leg" design: checkbox overlay, outline badge, times row. */
7558
+ class SbdReissueLegsStepComponent {
7559
+ constructor() {
7560
+ this.rows = input.required();
7561
+ this.anyAvailable = input(true);
7562
+ this.unavailableText = input('None of these flights can be changed online. Handle this booking manually.');
7563
+ /** Extra info line, e.g. "these flights must be upgraded together". */
7564
+ this.note = input(null);
7565
+ this.error = input(null);
7566
+ this.toggled = output();
7567
+ }
7568
+ /** The note under a disabled leg. */
7569
+ reasonText(reason) {
7570
+ switch (reason) {
7571
+ case 'flown': return 'This flight has already departed.';
7572
+ case 'no-options': return 'No upgrade options are available for this flight.';
7573
+ default: return 'This flight doesn’t support online reissue.';
7574
+ }
7575
+ }
7576
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdReissueLegsStepComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7577
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SbdReissueLegsStepComponent, isStandalone: true, selector: "app-sbd-reissue-legs-step", inputs: { rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, anyAvailable: { classPropertyName: "anyAvailable", publicName: "anyAvailable", isSignal: true, isRequired: false, transformFunction: null }, unavailableText: { classPropertyName: "unavailableText", publicName: "unavailableText", isSignal: true, isRequired: false, transformFunction: null }, note: { classPropertyName: "note", publicName: "note", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { toggled: "toggled" }, host: { classAttribute: "sbd-rlegs" }, ngImport: i0, template: `
7578
+ @if (!anyAvailable()) {
7579
+ <app-sbd-reissue-notice tone="warn" icon="pi pi-exclamation-triangle">{{ unavailableText() }}</app-sbd-reissue-notice>
7580
+ }
7581
+ @if (note()) {
7582
+ <app-sbd-reissue-notice icon="pi pi-info-circle">{{ note() }}</app-sbd-reissue-notice>
7583
+ }
7584
+ @if (error()) {
7585
+ <app-sbd-reissue-notice tone="error" icon="pi pi-exclamation-circle">{{ error() }}</app-sbd-reissue-notice>
7586
+ }
7587
+
7588
+ @for (row of rows(); track row.leg.legId) {
7589
+ <div class="sbd-rlegs__card" role="checkbox" tabindex="0"
7590
+ [attr.aria-checked]="row.selected" [attr.aria-disabled]="!row.available"
7591
+ [class.sbd-rlegs__card--selected]="row.selected"
7592
+ [class.sbd-rlegs__card--disabled]="!row.available"
7593
+ (click)="toggled.emit(row.leg.legId)"
7594
+ (keydown.enter)="toggled.emit(row.leg.legId)"
7595
+ (keydown.space)="$event.preventDefault(); toggled.emit(row.leg.legId)">
7596
+
7597
+ <span class="sbd-rlegs__checkbox" [class.sbd-rlegs__checkbox--on]="row.selected" [class.sbd-rlegs__checkbox--off]="!row.available">
7598
+ <i class="pi pi-check"></i>
7599
+ </span>
7600
+
7601
+ <div class="sbd-rlegs__content">
7602
+ <div class="sbd-rlegs__top">
7603
+ <div>
7604
+ <span class="sbd-rlegs__name">{{ row.from }} to {{ row.to }}</span>
7605
+ <div class="sbd-rlegs__date">{{ row.direction ? row.direction + ' · ' : '' }}{{ row.dateLabel }}</div>
7606
+ </div>
7607
+ <span class="sbd-rlegs__badge" [class.sbd-rlegs__badge--green]="row.available">
7608
+ {{ row.reason === 'flown' ? 'Used Flight' : row.reason === 'offline' ? 'Not supported' : row.reason ? 'Not available' : 'Available' }}
7609
+ </span>
7610
+ </div>
7611
+
7612
+ <div class="sbd-rlegs__times">
7613
+ <span class="sbd-rlegs__time">{{ row.depTime }}</span>
7614
+ <span class="sbd-rlegs__line">
7615
+ <span class="sbd-rlegs__stops">{{ row.stopsLabel }}</span>
7616
+ <i class="pi pi-arrow-right sbd-rlegs__arrow"></i>
7617
+ </span>
7618
+ <span class="sbd-rlegs__time">{{ row.arrTime }}</span>
7619
+ </div>
7620
+
7621
+ @if (row.airlineLine) { <span class="sbd-rlegs__airline">{{ row.airlineLine }}</span> }
7622
+
7623
+ @if (!row.available) {
7624
+ <div class="sbd-rlegs__notice">
7625
+ <i class="pi pi-info-circle"></i>
7626
+ <span>{{ reasonText(row.reason) }}</span>
7627
+ </div>
7628
+ }
7629
+ </div>
7630
+ </div>
7631
+ }
7632
+ `, isInline: true, styles: [":host{display:flex;flex-direction:column;gap:12px}.sbd-rlegs__card{border:1.5px solid #e2e8f0;border-radius:16px;background:#fff;transition:border-color .15s,box-shadow .15s;position:relative;padding:16px 16px 16px 48px;cursor:pointer}.sbd-rlegs__card:focus-visible{outline:2px solid var(--sbd-accent, #6d4aff);outline-offset:2px}.sbd-rlegs__card--selected{border-color:var(--sbd-accent, #6d4aff)}.sbd-rlegs__card--disabled{opacity:.7;cursor:not-allowed}.sbd-rlegs__checkbox{position:absolute;top:16px;left:16px;width:22px;height:22px;border-radius:6px;border:1.5px solid #cbd5e1;background:#fff;color:#fff;font-size:12px;display:flex;align-items:center;justify-content:center}.sbd-rlegs__checkbox--on{background:var(--sbd-accent, #6d4aff);border-color:var(--sbd-accent, #6d4aff)}.sbd-rlegs__checkbox--off{background:#f1f5f9;border-color:#e2e8f0}.sbd-rlegs__content{display:flex;flex-direction:column;gap:8px}.sbd-rlegs__top{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}.sbd-rlegs__name{font-size:15px;font-weight:700;color:#1e2e4a}.sbd-rlegs__date{font-size:13px;color:#64748b;margin-top:2px}.sbd-rlegs__badge{font-size:11.5px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;border-radius:20px;padding:4px 14px;width:max-content;background:#f8fafc;color:#64748b;box-shadow:inset 0 0 0 1px #e2e8f0}.sbd-rlegs__badge--green{background:transparent;color:#16a34a;box-shadow:inset 0 0 0 1.5px currentColor}.sbd-rlegs__times{display:flex;align-items:center;gap:8px}.sbd-rlegs__time{font-size:18px;font-weight:700;color:#1e2e4a;font-variant-numeric:tabular-nums}.sbd-rlegs__line{flex:1;position:relative;display:flex;flex-direction:column;align-items:center;gap:2px;padding:0 16px 0 8px}.sbd-rlegs__line:before{content:\"\";position:absolute;left:0;right:14px;top:50%;height:2px;background:#cbd5e1;z-index:1}.sbd-rlegs__stops{font-size:12px;color:#64748b;background:#fff;padding:0 6px;z-index:2}.sbd-rlegs__arrow{position:absolute;right:4px;top:51%;transform:translateY(-50%);font-size:11px;color:#64748b}.sbd-rlegs__airline{font-size:12.5px;color:#64748b}.sbd-rlegs__notice{display:flex;align-items:flex-start;gap:8px;padding:10px 12px;border-radius:10px;background:#fdf1e0;color:#6b4a16;font-size:12.5px;line-height:1.4}.sbd-rlegs__notice i{flex-shrink:0;margin-top:1px;color:#b98a3e}\n"], dependencies: [{ kind: "component", type: SbdReissueNoticeComponent, selector: "app-sbd-reissue-notice", inputs: ["icon", "tone"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7633
+ }
7634
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdReissueLegsStepComponent, decorators: [{
7635
+ type: Component,
7636
+ args: [{ selector: 'app-sbd-reissue-legs-step', standalone: true, imports: [SbdReissueNoticeComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
7637
+ @if (!anyAvailable()) {
7638
+ <app-sbd-reissue-notice tone="warn" icon="pi pi-exclamation-triangle">{{ unavailableText() }}</app-sbd-reissue-notice>
7639
+ }
7640
+ @if (note()) {
7641
+ <app-sbd-reissue-notice icon="pi pi-info-circle">{{ note() }}</app-sbd-reissue-notice>
7642
+ }
7643
+ @if (error()) {
7644
+ <app-sbd-reissue-notice tone="error" icon="pi pi-exclamation-circle">{{ error() }}</app-sbd-reissue-notice>
7645
+ }
7646
+
7647
+ @for (row of rows(); track row.leg.legId) {
7648
+ <div class="sbd-rlegs__card" role="checkbox" tabindex="0"
7649
+ [attr.aria-checked]="row.selected" [attr.aria-disabled]="!row.available"
7650
+ [class.sbd-rlegs__card--selected]="row.selected"
7651
+ [class.sbd-rlegs__card--disabled]="!row.available"
7652
+ (click)="toggled.emit(row.leg.legId)"
7653
+ (keydown.enter)="toggled.emit(row.leg.legId)"
7654
+ (keydown.space)="$event.preventDefault(); toggled.emit(row.leg.legId)">
7655
+
7656
+ <span class="sbd-rlegs__checkbox" [class.sbd-rlegs__checkbox--on]="row.selected" [class.sbd-rlegs__checkbox--off]="!row.available">
7657
+ <i class="pi pi-check"></i>
7658
+ </span>
7659
+
7660
+ <div class="sbd-rlegs__content">
7661
+ <div class="sbd-rlegs__top">
7662
+ <div>
7663
+ <span class="sbd-rlegs__name">{{ row.from }} to {{ row.to }}</span>
7664
+ <div class="sbd-rlegs__date">{{ row.direction ? row.direction + ' · ' : '' }}{{ row.dateLabel }}</div>
7665
+ </div>
7666
+ <span class="sbd-rlegs__badge" [class.sbd-rlegs__badge--green]="row.available">
7667
+ {{ row.reason === 'flown' ? 'Used Flight' : row.reason === 'offline' ? 'Not supported' : row.reason ? 'Not available' : 'Available' }}
7668
+ </span>
7669
+ </div>
7670
+
7671
+ <div class="sbd-rlegs__times">
7672
+ <span class="sbd-rlegs__time">{{ row.depTime }}</span>
7673
+ <span class="sbd-rlegs__line">
7674
+ <span class="sbd-rlegs__stops">{{ row.stopsLabel }}</span>
7675
+ <i class="pi pi-arrow-right sbd-rlegs__arrow"></i>
7676
+ </span>
7677
+ <span class="sbd-rlegs__time">{{ row.arrTime }}</span>
7678
+ </div>
7679
+
7680
+ @if (row.airlineLine) { <span class="sbd-rlegs__airline">{{ row.airlineLine }}</span> }
7681
+
7682
+ @if (!row.available) {
7683
+ <div class="sbd-rlegs__notice">
7684
+ <i class="pi pi-info-circle"></i>
7685
+ <span>{{ reasonText(row.reason) }}</span>
7686
+ </div>
7687
+ }
7688
+ </div>
7689
+ </div>
7690
+ }
7691
+ `, host: { class: 'sbd-rlegs' }, styles: [":host{display:flex;flex-direction:column;gap:12px}.sbd-rlegs__card{border:1.5px solid #e2e8f0;border-radius:16px;background:#fff;transition:border-color .15s,box-shadow .15s;position:relative;padding:16px 16px 16px 48px;cursor:pointer}.sbd-rlegs__card:focus-visible{outline:2px solid var(--sbd-accent, #6d4aff);outline-offset:2px}.sbd-rlegs__card--selected{border-color:var(--sbd-accent, #6d4aff)}.sbd-rlegs__card--disabled{opacity:.7;cursor:not-allowed}.sbd-rlegs__checkbox{position:absolute;top:16px;left:16px;width:22px;height:22px;border-radius:6px;border:1.5px solid #cbd5e1;background:#fff;color:#fff;font-size:12px;display:flex;align-items:center;justify-content:center}.sbd-rlegs__checkbox--on{background:var(--sbd-accent, #6d4aff);border-color:var(--sbd-accent, #6d4aff)}.sbd-rlegs__checkbox--off{background:#f1f5f9;border-color:#e2e8f0}.sbd-rlegs__content{display:flex;flex-direction:column;gap:8px}.sbd-rlegs__top{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}.sbd-rlegs__name{font-size:15px;font-weight:700;color:#1e2e4a}.sbd-rlegs__date{font-size:13px;color:#64748b;margin-top:2px}.sbd-rlegs__badge{font-size:11.5px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;border-radius:20px;padding:4px 14px;width:max-content;background:#f8fafc;color:#64748b;box-shadow:inset 0 0 0 1px #e2e8f0}.sbd-rlegs__badge--green{background:transparent;color:#16a34a;box-shadow:inset 0 0 0 1.5px currentColor}.sbd-rlegs__times{display:flex;align-items:center;gap:8px}.sbd-rlegs__time{font-size:18px;font-weight:700;color:#1e2e4a;font-variant-numeric:tabular-nums}.sbd-rlegs__line{flex:1;position:relative;display:flex;flex-direction:column;align-items:center;gap:2px;padding:0 16px 0 8px}.sbd-rlegs__line:before{content:\"\";position:absolute;left:0;right:14px;top:50%;height:2px;background:#cbd5e1;z-index:1}.sbd-rlegs__stops{font-size:12px;color:#64748b;background:#fff;padding:0 6px;z-index:2}.sbd-rlegs__arrow{position:absolute;right:4px;top:51%;transform:translateY(-50%);font-size:11px;color:#64748b}.sbd-rlegs__airline{font-size:12.5px;color:#64748b}.sbd-rlegs__notice{display:flex;align-items:flex-start;gap:8px;padding:10px 12px;border-radius:10px;background:#fdf1e0;color:#6b4a16;font-size:12.5px;line-height:1.4}.sbd-rlegs__notice i{flex-shrink:0;margin-top:1px;color:#b98a3e}\n"] }]
7692
+ }] });
7693
+
7694
+ class SbdReissueDateStepComponent {
7695
+ constructor() {
7696
+ this.rows = input.required();
7697
+ this.changed = output();
7698
+ }
7699
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdReissueDateStepComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7700
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SbdReissueDateStepComponent, isStandalone: true, selector: "app-sbd-reissue-date-step", inputs: { rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { changed: "changed" }, host: { classAttribute: "sbd-rdate" }, ngImport: i0, template: `
7701
+ @for (row of rows(); track row.leg.legId) {
7702
+ <div class="sbd-rdate__card">
7703
+ <div class="sbd-rdate__head">
7704
+ <span class="sbd-rdate__name">{{ row.leg.originCode }} to {{ row.leg.destinationCode }}</span>
7705
+ <span class="sbd-rdate__current">Currently {{ row.current }}</span>
7706
+ </div>
7707
+ <label class="sbd-rdate__field">
7708
+ <span>New departure date</span>
7709
+ <input type="date" class="sbd-rdate__input"
7710
+ [value]="row.value" [min]="row.min" [attr.max]="row.max || null"
7711
+ (input)="changed.emit({ legId: row.leg.legId, date: $any($event.target).value })" />
7712
+ </label>
7713
+ </div>
7714
+ }
7715
+ `, isInline: true, styles: [":host{display:flex;flex-direction:column;gap:12px}.sbd-rdate__card{border:1.5px solid #e2e8f0;border-radius:16px;background:#fff;transition:border-color .15s,box-shadow .15s;padding:16px;display:flex;flex-direction:column;gap:14px}.sbd-rdate__head{display:flex;flex-direction:column;gap:2px}.sbd-rdate__name{font-size:15px;font-weight:700;color:#1e2e4a}.sbd-rdate__current{font-size:13px;color:#64748b}.sbd-rdate__field{display:flex;flex-direction:column;gap:6px;min-width:0;font-size:12.5px;font-weight:600;color:#475569}.sbd-rdate__input{height:40px;padding:0 12px;border:1.5px solid #e2e8f0;border-radius:10px;background:#fff;font-size:13px;font-family:inherit;color:#1e2e4a}.sbd-rdate__input:focus{outline:none;border-color:var(--sbd-accent, #6d4aff)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7716
+ }
7717
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdReissueDateStepComponent, decorators: [{
7718
+ type: Component,
7719
+ args: [{ selector: 'app-sbd-reissue-date-step', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
7720
+ @for (row of rows(); track row.leg.legId) {
7721
+ <div class="sbd-rdate__card">
7722
+ <div class="sbd-rdate__head">
7723
+ <span class="sbd-rdate__name">{{ row.leg.originCode }} to {{ row.leg.destinationCode }}</span>
7724
+ <span class="sbd-rdate__current">Currently {{ row.current }}</span>
7725
+ </div>
7726
+ <label class="sbd-rdate__field">
7727
+ <span>New departure date</span>
7728
+ <input type="date" class="sbd-rdate__input"
7729
+ [value]="row.value" [min]="row.min" [attr.max]="row.max || null"
7730
+ (input)="changed.emit({ legId: row.leg.legId, date: $any($event.target).value })" />
7731
+ </label>
7732
+ </div>
7733
+ }
7734
+ `, host: { class: 'sbd-rdate' }, styles: [":host{display:flex;flex-direction:column;gap:12px}.sbd-rdate__card{border:1.5px solid #e2e8f0;border-radius:16px;background:#fff;transition:border-color .15s,box-shadow .15s;padding:16px;display:flex;flex-direction:column;gap:14px}.sbd-rdate__head{display:flex;flex-direction:column;gap:2px}.sbd-rdate__name{font-size:15px;font-weight:700;color:#1e2e4a}.sbd-rdate__current{font-size:13px;color:#64748b}.sbd-rdate__field{display:flex;flex-direction:column;gap:6px;min-width:0;font-size:12.5px;font-weight:600;color:#475569}.sbd-rdate__input{height:40px;padding:0 12px;border:1.5px solid #e2e8f0;border-radius:10px;background:#fff;font-size:13px;font-family:inherit;color:#1e2e4a}.sbd-rdate__input:focus{outline:none;border-color:var(--sbd-accent, #6d4aff)}\n"] }]
7735
+ }] });
7736
+
7737
+ class SbdReissueRouteStepComponent {
7738
+ constructor() {
7739
+ this.rows = input.required();
7740
+ this.options = input.required();
7741
+ this.airportChanged = output();
7742
+ this.searched = output();
7743
+ this.swapped = output();
7744
+ this.compare = compareAirports;
7745
+ this.sides = [
7746
+ { key: 'origin', label: 'From' },
7747
+ { key: 'destination', label: 'To' },
7748
+ ];
7749
+ }
7750
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdReissueRouteStepComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7751
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SbdReissueRouteStepComponent, isStandalone: true, selector: "app-sbd-reissue-route-step", inputs: { rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { airportChanged: "airportChanged", searched: "searched", swapped: "swapped" }, host: { classAttribute: "sbd-rroute" }, ngImport: i0, template: `
7752
+ @for (row of rows(); track row.leg.legId) {
7753
+ <div class="sbd-rroute__card">
7754
+ <div class="sbd-rroute__head">
7755
+ <span class="sbd-rroute__name">{{ row.leg.originCode }} to {{ row.leg.destinationCode }}</span>
7756
+ <span class="sbd-rroute__current">Currently {{ row.current }}</span>
7757
+ </div>
7758
+
7759
+ <div class="sbd-rroute__pair">
7760
+ @for (side of sides; track side.key) {
7761
+ <label class="sbd-rroute__field">
7762
+ <span>{{ side.label }}</span>
7763
+ <ng-select class="sbd-rroute__select" bindLabel="label" [clearable]="false"
7764
+ [items]="options()" [compareWith]="compare" [ngModel]="row[side.key]"
7765
+ (ngModelChange)="airportChanged.emit({ legId: row.leg.legId, side: side.key, airport: $event })"
7766
+ (search)="searched.emit($event.term)">
7767
+ <ng-template ng-label-tmp let-item="item">{{ item.city_name }} ({{ item.id }})</ng-template>
7768
+ <ng-template ng-option-tmp let-item="item">
7769
+ <div class="sbd-rroute__opt"><b>{{ item.city_name }} ({{ item.id }})</b><small>{{ item.name }}</small></div>
7770
+ </ng-template>
7771
+ </ng-select>
7772
+ </label>
7773
+
7774
+ <!-- Swap arrow, between From and To -->
7775
+ @if ($first) {
7776
+ <button type="button" class="sbd-rroute__swap" aria-label="Swap origin and destination"
7777
+ (click)="swapped.emit(row.leg.legId)">
7778
+ <!-- Inline SVG: primeicons 4.x has no horizontal swap arrows. -->
7779
+ <svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.6"
7780
+ stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
7781
+ <path d="M2 5.5h11M10.5 3l2.5 2.5-2.5 2.5M14 10.5H3M5.5 8 3 10.5 5.5 13" />
7782
+ </svg>
7783
+ </button>
7784
+ }
7785
+ }
7786
+ </div>
7787
+ </div>
7788
+ }
7789
+ `, isInline: true, styles: [":host{display:flex;flex-direction:column;gap:12px}.sbd-rroute__card{border:1.5px solid #e2e8f0;border-radius:16px;background:#fff;transition:border-color .15s,box-shadow .15s;padding:16px;display:flex;flex-direction:column;gap:14px}.sbd-rroute__head{display:flex;flex-direction:column;gap:2px}.sbd-rroute__name{font-size:15px;font-weight:700;color:#1e2e4a}.sbd-rroute__current{font-size:13px;color:#64748b}.sbd-rroute__field{display:flex;flex-direction:column;gap:6px;min-width:0;font-size:12.5px;font-weight:600;color:#475569}.sbd-rroute__pair{display:grid;grid-template-columns:minmax(0,1fr) auto minmax(0,1fr);gap:8px;align-items:end}.sbd-rroute__swap{width:32px;height:32px;margin-bottom:4px;border-radius:50%;border:1.5px solid #e2e8f0;background:#fff;color:#1e2e4a;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background .15s,border-color .15s}.sbd-rroute__swap .pi{font-size:13px}.sbd-rroute__swap:hover{background:#f8fafc;border-color:var(--sbd-accent, #6d4aff)}.sbd-rroute__opt{display:flex;flex-direction:column;line-height:1.3;min-width:0}.sbd-rroute__opt b{font-size:13px;font-weight:600;color:#1e2e4a}.sbd-rroute__opt small{font-size:11px;color:#64748b;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host ::ng-deep .sbd-rroute__select{font-size:13px;font-weight:400;color:#1e2e4a}:host ::ng-deep .sbd-rroute__select .ng-select-container{min-height:40px;border-radius:10px;border:1.5px solid #e2e8f0}:host ::ng-deep .sbd-rroute__select.ng-select-focused:not(.ng-select-opened)>.ng-select-container{border-color:var(--sbd-accent, #6d4aff);box-shadow:none}:host ::ng-deep .sbd-rroute__select .ng-value-container .ng-value{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}:host ::ng-deep .sbd-rroute__select .ng-dropdown-panel{min-width:100%;z-index:1100;border-radius:10px}:host ::ng-deep .sbd-rroute__select .ng-dropdown-panel .ng-dropdown-panel-items{max-height:220px}:host ::ng-deep .sbd-rroute__select .ng-dropdown-panel .ng-option{padding:8px 12px;font-size:13px;white-space:normal}:host ::ng-deep .sbd-rroute__select .ng-dropdown-panel .ng-option.ng-option-selected,:host ::ng-deep .sbd-rroute__select .ng-dropdown-panel .ng-option.ng-option-marked{background:var(--sbd-accent-soft, #ede9fe)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: NgSelectModule }, { kind: "component", type: i5$1.NgSelectComponent, selector: "ng-select", inputs: ["bindLabel", "bindValue", "ariaLabel", "markFirst", "placeholder", "fixedPlaceholder", "notFoundText", "typeToSearchText", "preventToggleOnRightClick", "addTagText", "loadingText", "clearAllText", "appearance", "dropdownPosition", "appendTo", "loading", "closeOnSelect", "hideSelected", "selectOnTab", "openOnEnter", "maxSelectedItems", "groupBy", "groupValue", "bufferAmount", "virtualScroll", "selectableGroup", "selectableGroupAsModel", "searchFn", "trackByFn", "clearOnBackspace", "labelForId", "inputAttrs", "tabIndex", "readonly", "searchWhileComposing", "minTermLength", "editableSearchTerm", "ngClass", "typeahead", "multiple", "addTag", "searchable", "clearable", "isOpen", "items", "compareWith", "clearSearchOnAdd", "deselectOnClick", "keyDownFn"], outputs: ["blur", "focus", "change", "open", "close", "search", "clear", "add", "remove", "scroll", "scrollToEnd"] }, { kind: "directive", type: i5$1.NgOptionTemplateDirective, selector: "[ng-option-tmp]" }, { kind: "directive", type: i5$1.NgLabelTemplateDirective, selector: "[ng-label-tmp]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7790
+ }
7791
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdReissueRouteStepComponent, decorators: [{
7792
+ type: Component,
7793
+ args: [{ selector: 'app-sbd-reissue-route-step', standalone: true, imports: [FormsModule, NgSelectModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
7794
+ @for (row of rows(); track row.leg.legId) {
7795
+ <div class="sbd-rroute__card">
7796
+ <div class="sbd-rroute__head">
7797
+ <span class="sbd-rroute__name">{{ row.leg.originCode }} to {{ row.leg.destinationCode }}</span>
7798
+ <span class="sbd-rroute__current">Currently {{ row.current }}</span>
7799
+ </div>
7800
+
7801
+ <div class="sbd-rroute__pair">
7802
+ @for (side of sides; track side.key) {
7803
+ <label class="sbd-rroute__field">
7804
+ <span>{{ side.label }}</span>
7805
+ <ng-select class="sbd-rroute__select" bindLabel="label" [clearable]="false"
7806
+ [items]="options()" [compareWith]="compare" [ngModel]="row[side.key]"
7807
+ (ngModelChange)="airportChanged.emit({ legId: row.leg.legId, side: side.key, airport: $event })"
7808
+ (search)="searched.emit($event.term)">
7809
+ <ng-template ng-label-tmp let-item="item">{{ item.city_name }} ({{ item.id }})</ng-template>
7810
+ <ng-template ng-option-tmp let-item="item">
7811
+ <div class="sbd-rroute__opt"><b>{{ item.city_name }} ({{ item.id }})</b><small>{{ item.name }}</small></div>
7812
+ </ng-template>
7813
+ </ng-select>
7814
+ </label>
7815
+
7816
+ <!-- Swap arrow, between From and To -->
7817
+ @if ($first) {
7818
+ <button type="button" class="sbd-rroute__swap" aria-label="Swap origin and destination"
7819
+ (click)="swapped.emit(row.leg.legId)">
7820
+ <!-- Inline SVG: primeicons 4.x has no horizontal swap arrows. -->
7821
+ <svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.6"
7822
+ stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
7823
+ <path d="M2 5.5h11M10.5 3l2.5 2.5-2.5 2.5M14 10.5H3M5.5 8 3 10.5 5.5 13" />
7824
+ </svg>
7825
+ </button>
7826
+ }
7827
+ }
7828
+ </div>
7829
+ </div>
7830
+ }
7831
+ `, host: { class: 'sbd-rroute' }, styles: [":host{display:flex;flex-direction:column;gap:12px}.sbd-rroute__card{border:1.5px solid #e2e8f0;border-radius:16px;background:#fff;transition:border-color .15s,box-shadow .15s;padding:16px;display:flex;flex-direction:column;gap:14px}.sbd-rroute__head{display:flex;flex-direction:column;gap:2px}.sbd-rroute__name{font-size:15px;font-weight:700;color:#1e2e4a}.sbd-rroute__current{font-size:13px;color:#64748b}.sbd-rroute__field{display:flex;flex-direction:column;gap:6px;min-width:0;font-size:12.5px;font-weight:600;color:#475569}.sbd-rroute__pair{display:grid;grid-template-columns:minmax(0,1fr) auto minmax(0,1fr);gap:8px;align-items:end}.sbd-rroute__swap{width:32px;height:32px;margin-bottom:4px;border-radius:50%;border:1.5px solid #e2e8f0;background:#fff;color:#1e2e4a;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background .15s,border-color .15s}.sbd-rroute__swap .pi{font-size:13px}.sbd-rroute__swap:hover{background:#f8fafc;border-color:var(--sbd-accent, #6d4aff)}.sbd-rroute__opt{display:flex;flex-direction:column;line-height:1.3;min-width:0}.sbd-rroute__opt b{font-size:13px;font-weight:600;color:#1e2e4a}.sbd-rroute__opt small{font-size:11px;color:#64748b;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host ::ng-deep .sbd-rroute__select{font-size:13px;font-weight:400;color:#1e2e4a}:host ::ng-deep .sbd-rroute__select .ng-select-container{min-height:40px;border-radius:10px;border:1.5px solid #e2e8f0}:host ::ng-deep .sbd-rroute__select.ng-select-focused:not(.ng-select-opened)>.ng-select-container{border-color:var(--sbd-accent, #6d4aff);box-shadow:none}:host ::ng-deep .sbd-rroute__select .ng-value-container .ng-value{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}:host ::ng-deep .sbd-rroute__select .ng-dropdown-panel{min-width:100%;z-index:1100;border-radius:10px}:host ::ng-deep .sbd-rroute__select .ng-dropdown-panel .ng-dropdown-panel-items{max-height:220px}:host ::ng-deep .sbd-rroute__select .ng-dropdown-panel .ng-option{padding:8px 12px;font-size:13px;white-space:normal}:host ::ng-deep .sbd-rroute__select .ng-dropdown-panel .ng-option.ng-option-selected,:host ::ng-deep .sbd-rroute__select .ng-dropdown-panel .ng-option.ng-option-marked{background:var(--sbd-accent-soft, #ede9fe)}\n"] }]
7832
+ }] });
7833
+
7834
+ /**
7835
+ * Reissue dialog shell: owns the p-dialog, the loading / error states and the footer, and swaps the step
7836
+ * components. All state and rules live in `SbdReissueStore`; the steps only render and emit.
7837
+ */
7838
+ class SbdReissueDialogComponent {
7839
+ constructor() {
7840
+ this.store = inject(SbdReissueStore);
7841
+ this.upgrade = inject(SbdReissueUpgradeStore);
7842
+ this.config = inject(SBD_CONFIG);
7843
+ this.injector = inject(Injector);
7844
+ this.visible = input(false);
7845
+ this.bookingId = input('');
7846
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7847
+ this.bookingData = input(null);
7848
+ this.closed = output();
7849
+ /** Header title per screen, like the cancel dialog's "Select Leg to Cancel" / "Refund Method". */
7850
+ this.title = computed(() => {
7851
+ switch (this.store.step()) {
7852
+ case 'legs': return 'Select Flight to Change';
7853
+ case 'edit': return this.store.service() === 'change_date' ? 'Choose New Date' : 'Choose New Route';
7854
+ case 'upgrade-legs': return 'Select Flight to Upgrade';
7855
+ case 'upgrade': return 'Choose New Class';
7856
+ case 'confirm': return 'Review Upgrade';
7857
+ default: return 'Reissue Booking';
7858
+ }
7859
+ });
7860
+ /** Gray hint above the footer (cancel's "Select at least one flight to continue"), or why Search is disabled. */
7861
+ this.hint = computed(() => {
7862
+ switch (this.store.step()) {
7863
+ case 'legs':
7864
+ return this.store.anyAvailable() && !this.store.canContinue() ? 'Select at least one flight to continue' : null;
7865
+ case 'edit':
7866
+ return this.store.validationError();
7867
+ case 'upgrade-legs':
7868
+ return this.upgrade.anyLegUpgradable() && !this.upgrade.canContinueFromLegs() && !this.upgrade.submitting()
7869
+ ? 'Select at least one flight to continue'
7870
+ : null;
7871
+ case 'upgrade':
7872
+ return this.upgrade.sections().some(s => s.options.length) && !this.upgrade.canContinue() && !this.upgrade.submitting()
7873
+ ? 'Select a class to continue'
7874
+ : null;
7875
+ default:
7876
+ return null;
7877
+ }
7878
+ });
7879
+ // Depends only on these three inputs; whatever the store reads while opening must not retrigger it.
7880
+ effect(() => {
7881
+ const [visible, data, id] = [this.visible(), this.bookingData(), this.bookingId()];
7882
+ if (visible)
7883
+ untracked(() => this.store.open(data, id));
7884
+ }, { allowSignalWrites: true });
7885
+ }
7886
+ onServiceChosen(service) {
7887
+ if (service === 'upgrade_class')
7888
+ this.upgrade.start();
7889
+ else
7890
+ this.store.chooseService(service);
7891
+ }
7892
+ /** Back from the options returns to "select leg" only when that step was shown (the upgrade store knows). */
7893
+ onBack() {
7894
+ if (this.store.step() === 'upgrade')
7895
+ this.upgrade.back();
7896
+ else
7897
+ this.store.back();
7898
+ }
7899
+ /** The payment link expired: redo the whole reissue from the start (a new post-booking, the service list again). */
7900
+ restartFlow() {
7901
+ this.store.open(this.bookingData(), this.bookingId());
7902
+ }
7903
+ /** Redirects to the web search (new tab: the agent keeps the dashboard open next to the web flow). */
7904
+ async searchFlights() {
7905
+ const url = this.store.searchUrl();
7906
+ if (!url)
7907
+ return;
7908
+ // Open the tab now, inside the click, so the browser does not block it while the host's token is awaited.
7909
+ const tab = window.open('about:blank', '_blank');
7910
+ const finalUrl = appendAuthParams(url, await this.redirectAuthParams(), this.config.redirectAuthIn);
7911
+ if (tab) {
7912
+ tab.opener = null; // same protection as `noopener`
7913
+ tab.location.href = finalUrl;
7914
+ }
7915
+ else {
7916
+ window.open(finalUrl, '_blank', 'noopener');
7917
+ }
7918
+ this.closed.emit();
7919
+ }
7920
+ /** The host's auth parameters for the web link; `{}` when it has none or it fails (the web then asks to log in). */
7921
+ async redirectAuthParams() {
7922
+ const provide = this.config.redirectAuthParams;
7923
+ if (!provide)
7924
+ return {};
7925
+ try {
7926
+ const result = runInInjectionContext(this.injector, provide);
7927
+ return (isObservable(result) ? await firstValueFrom(result) : await result) ?? {};
7928
+ }
7929
+ catch {
7930
+ return {};
7931
+ }
7932
+ }
7933
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdReissueDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7934
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SbdReissueDialogComponent, isStandalone: true, selector: "app-sbd-reissue-dialog", inputs: { visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, bookingId: { classPropertyName: "bookingId", publicName: "bookingId", isSignal: true, isRequired: false, transformFunction: null }, bookingData: { classPropertyName: "bookingData", publicName: "bookingData", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, providers: [SbdReissueStore, SbdReissueUpgradeStore], ngImport: i0, template: "<p-dialog [visible]=\"visible()\" (visibleChange)=\"$event ? null : closed.emit()\"\n [modal]=\"true\" [blockScroll]=\"true\" [draggable]=\"false\" [resizable]=\"false\"\n [showHeader]=\"false\" [style]=\"{ width: '560px', padding: '0' }\" styleClass=\"sbd-reissue-dialog-wrapper\">\n\n <div class=\"sbd-reissue\">\n\n <!-- Header: same layout as the cancel dialog (round close button, centered title + booking id) -->\n <div class=\"sbd-reissue__header\">\n <button type=\"button\" class=\"sbd-reissue__close\" (click)=\"closed.emit()\" aria-label=\"Close\">\n <i class=\"pi pi-times\"></i>\n </button>\n <div class=\"sbd-reissue__header-text\">\n <h3 class=\"sbd-reissue__title\">{{ title() }}</h3>\n <span class=\"sbd-reissue__sub\">Booking ID #{{ bookingId() }}</span>\n </div>\n </div>\n\n @if (store.loading()) {\n <div class=\"sbd-reissue__state\">\n <div class=\"sbd-reissue__state-icon\"><i class=\"pi pi-spin pi-spinner\"></i></div>\n <div class=\"sbd-reissue__state-text\">Checking availability\u2026</div>\n <div class=\"sbd-reissue__dots\"><span></span><span></span><span></span></div>\n </div>\n } @else if (store.error()) {\n <div class=\"sbd-reissue__state\">\n <div class=\"sbd-reissue__state-icon sbd-reissue__state-icon--warn\"><i class=\"pi pi-exclamation-triangle\"></i></div>\n <div class=\"sbd-reissue__state-text\">{{ store.error() }}</div>\n <button pButton type=\"button\" label=\"Retry\" class=\"p-button-outlined p-button-secondary\" (click)=\"store.load()\"></button>\n </div>\n } @else if (store.blocked()) {\n <!-- No leg can be reissued online: same idea as the cancel dialog's \"not eligible, escalate\" notice. -->\n <div class=\"sbd-reissue__body\">\n <app-sbd-reissue-notice tone=\"warn\" icon=\"pi pi-exclamation-triangle\">\n <strong>{{ store.blocked()?.title }}</strong><br>{{ store.blocked()?.text }}\n </app-sbd-reissue-notice>\n </div>\n <div class=\"sbd-reissue__footer\">\n <button pButton type=\"button\" label=\"Close\" class=\"p-button-outlined p-button-secondary sbd-reissue__btn\"\n (click)=\"closed.emit()\"></button>\n </div>\n } @else {\n <div class=\"sbd-reissue__body\">\n @switch (store.step()) {\n @case ('service') {\n <app-sbd-reissue-notice>Change requests are subject to availability from the airline.</app-sbd-reissue-notice>\n <app-sbd-reissue-service-step\n [upgradeAvailable]=\"store.upgradeAvailable()\"\n (chosen)=\"onServiceChosen($event)\" />\n }\n @case ('legs') {\n <app-sbd-reissue-legs-step\n [rows]=\"store.legRows()\"\n [anyAvailable]=\"store.anyAvailable()\"\n (toggled)=\"store.toggleLeg($event)\" />\n }\n @case ('edit') {\n @if (store.service() === 'change_date') {\n <app-sbd-reissue-date-step\n [rows]=\"store.dateRows()\"\n (changed)=\"store.setDate($event.legId, $event.date)\" />\n } @else {\n <app-sbd-reissue-route-step\n [rows]=\"store.routeRows()\"\n [options]=\"store.airportOptions()\"\n (airportChanged)=\"store.setAirport($event.legId, $event.side, $event.airport)\"\n (searched)=\"store.searchAirports($event)\"\n (swapped)=\"store.swapAirports($event)\" />\n }\n }\n @case ('upgrade-legs') {\n <app-sbd-reissue-legs-step\n unavailableText=\"None of these flights can be upgraded online. Handle this booking manually.\"\n [rows]=\"upgrade.legRows()\"\n [anyAvailable]=\"upgrade.anyLegUpgradable()\"\n [note]=\"store.allowSingleLegSelection() ? null : 'These flights must be upgraded together.'\"\n [error]=\"upgrade.error()\"\n (toggled)=\"upgrade.toggleLeg($event)\" />\n }\n @case ('upgrade') {\n <app-sbd-reissue-upgrade-step\n [sections]=\"upgrade.sections()\"\n [error]=\"upgrade.error()\"\n (picked)=\"upgrade.select($event.sectionId, $event.key)\" />\n }\n @case ('confirm') {\n <app-sbd-reissue-upgrade-confirm-step\n [rows]=\"upgrade.summary()\"\n [total]=\"upgrade.total()\"\n [totalLabel]=\"upgrade.totalLabel()\"\n [priceChanged]=\"upgrade.priceChanged()\"\n [booked]=\"upgrade.booked()\"\n [link]=\"upgrade.paymentLink()\"\n [error]=\"upgrade.error()\"\n (restart)=\"restartFlow()\" />\n }\n }\n </div>\n\n @if (hint(); as text) {\n <div class=\"sbd-reissue__hint\">{{ text }}</div>\n }\n\n @if (store.step() !== 'service') {\n <div class=\"sbd-reissue__footer\">\n <button pButton type=\"button\" label=\"Back\" class=\"p-button-outlined p-button-secondary sbd-reissue__btn\"\n [disabled]=\"store.step() === 'confirm' && upgrade.locked()\" (click)=\"onBack()\"></button>\n\n @switch (store.step()) {\n @case ('legs') {\n <button pButton type=\"button\" label=\"Continue\" class=\"sbd-reissue__btn\"\n [disabled]=\"!store.canContinue()\" (click)=\"store.goToEdit()\"></button>\n }\n @case ('edit') {\n <button pButton type=\"button\" icon=\"pi pi-search\" label=\"Search flights\" class=\"sbd-reissue__btn\"\n [disabled]=\"!!store.validationError()\" (click)=\"searchFlights()\"></button>\n }\n @case ('upgrade-legs') {\n <button pButton type=\"button\" label=\"Continue\" class=\"sbd-reissue__btn\" [loading]=\"upgrade.submitting()\"\n [disabled]=\"!upgrade.canContinueFromLegs()\" (click)=\"upgrade.continueFromLegs()\"></button>\n }\n @case ('upgrade') {\n <button pButton type=\"button\" label=\"Continue\" class=\"sbd-reissue__btn\" [loading]=\"upgrade.submitting()\"\n [disabled]=\"!upgrade.canContinue()\" (click)=\"upgrade.continue()\"></button>\n }\n @case ('confirm') {\n @switch (upgrade.action()) {\n @case ('done') {\n <button pButton type=\"button\" label=\"Close\" class=\"sbd-reissue__btn\" (click)=\"closed.emit()\"></button>\n }\n @case ('book') {\n <button pButton type=\"button\" label=\"Confirm upgrade\" class=\"sbd-reissue__btn\"\n [loading]=\"upgrade.submitting()\" (click)=\"upgrade.confirm()\"></button>\n }\n @case ('link') {\n <button pButton type=\"button\" icon=\"pi pi-link\" label=\"Generate payment link\" class=\"sbd-reissue__btn\"\n [loading]=\"upgrade.submitting()\" (click)=\"upgrade.confirm()\"></button>\n }\n }\n }\n }\n </div>\n }\n }\n </div>\n</p-dialog>\n", styles: [":host ::ng-deep .sbd-reissue-dialog-wrapper{border-radius:16px;box-shadow:0 24px 60px #0f172a38}:host ::ng-deep .sbd-reissue-dialog-wrapper .p-dialog-content{padding:0;border-radius:16px;overflow:visible}.sbd-reissue{font-family:inherit;position:relative}.sbd-reissue__header{display:flex;align-items:flex-start;gap:12px;padding:20px 20px 0}.sbd-reissue__close{flex-shrink:0;width:32px;height:32px;border-radius:50%;background:#f8fafc;border:none;color:#1e2e4a;display:flex;align-items:center;justify-content:center;cursor:pointer}.sbd-reissue__close .pi{font-size:13px}.sbd-reissue__close:hover{background:#e2e8f0}.sbd-reissue__header-text{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px;text-align:center;margin-right:32px}.sbd-reissue__title{font-size:19px;font-weight:800;color:#1e2e4a;margin:0}.sbd-reissue__sub{font-size:12.5px;color:#94a3b8}.sbd-reissue__body{padding:16px 20px 20px;display:flex;flex-direction:column;gap:12px}.sbd-reissue__hint{text-align:center;padding:0 20px 12px;font-size:13px;color:#94a3b8}.sbd-reissue__footer{display:flex;justify-content:flex-end;gap:10px;align-items:stretch;padding:14px 20px;border-top:1px solid #e2e8f0;background:#fff;border-radius:0 0 16px 16px}.sbd-reissue__btn{flex:1 1 0;justify-content:center}.sbd-reissue__state{padding:36px 24px 28px;display:flex;flex-direction:column;align-items:center;gap:14px;text-align:center}.sbd-reissue__state-icon{width:56px;height:56px;border-radius:50%;background:#fff7ed;border:2px solid #fed7aa;display:flex;align-items:center;justify-content:center}.sbd-reissue__state-icon .pi{font-size:24px;color:#f97316}.sbd-reissue__state-icon--warn{background:#fef2f2;border-color:#fecaca}.sbd-reissue__state-icon--warn .pi{color:#ef4444}.sbd-reissue__state-text{font-size:13px;color:#64748b;min-height:20px}.sbd-reissue__dots{display:flex;gap:6px}.sbd-reissue__dots span{width:7px;height:7px;border-radius:50%;background:#f97316;opacity:.3;animation:sbd-reissue-dot 1.2s ease-in-out infinite}.sbd-reissue__dots span:nth-child(2){animation-delay:.2s}.sbd-reissue__dots span:nth-child(3){animation-delay:.4s}@keyframes sbd-reissue-dot{0%,to{opacity:.3;transform:scale(1)}50%{opacity:1;transform:scale(1.4)}}\n"], dependencies: [{ kind: "ngmodule", type: DialogModule }, { kind: "component", type: i1$1.Dialog, selector: "p-dialog", inputs: ["header", "draggable", "resizable", "positionLeft", "positionTop", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "responsive", "appendTo", "breakpoints", "styleClass", "maskStyleClass", "showHeader", "breakpoint", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "visible", "style", "position"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i2$1.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "label", "icon", "loading"] }, { kind: "component", type: SbdReissueNoticeComponent, selector: "app-sbd-reissue-notice", inputs: ["icon", "tone"] }, { kind: "component", type: SbdReissueServiceStepComponent, selector: "app-sbd-reissue-service-step", inputs: ["upgradeAvailable"], outputs: ["chosen"] }, { kind: "component", type: SbdReissueLegsStepComponent, selector: "app-sbd-reissue-legs-step", inputs: ["rows", "anyAvailable", "unavailableText", "note", "error"], outputs: ["toggled"] }, { kind: "component", type: SbdReissueDateStepComponent, selector: "app-sbd-reissue-date-step", inputs: ["rows"], outputs: ["changed"] }, { kind: "component", type: SbdReissueRouteStepComponent, selector: "app-sbd-reissue-route-step", inputs: ["rows", "options"], outputs: ["airportChanged", "searched", "swapped"] }, { kind: "component", type: SbdReissueUpgradeStepComponent, selector: "app-sbd-reissue-upgrade-step", inputs: ["sections", "error"], outputs: ["picked"] }, { kind: "component", type: SbdReissueUpgradeConfirmStepComponent, selector: "app-sbd-reissue-upgrade-confirm-step", inputs: ["rows", "total", "totalLabel", "priceChanged", "booked", "link", "error"], outputs: ["restart"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7935
+ }
7936
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdReissueDialogComponent, decorators: [{
7937
+ type: Component,
7938
+ args: [{ selector: 'app-sbd-reissue-dialog', standalone: true, imports: [
7939
+ DialogModule,
7940
+ ButtonModule,
7941
+ SbdReissueNoticeComponent,
7942
+ SbdReissueServiceStepComponent,
7943
+ SbdReissueLegsStepComponent,
7944
+ SbdReissueDateStepComponent,
7945
+ SbdReissueRouteStepComponent,
7946
+ SbdReissueUpgradeStepComponent,
7947
+ SbdReissueUpgradeConfirmStepComponent,
7948
+ ], providers: [SbdReissueStore, SbdReissueUpgradeStore], changeDetection: ChangeDetectionStrategy.OnPush, template: "<p-dialog [visible]=\"visible()\" (visibleChange)=\"$event ? null : closed.emit()\"\n [modal]=\"true\" [blockScroll]=\"true\" [draggable]=\"false\" [resizable]=\"false\"\n [showHeader]=\"false\" [style]=\"{ width: '560px', padding: '0' }\" styleClass=\"sbd-reissue-dialog-wrapper\">\n\n <div class=\"sbd-reissue\">\n\n <!-- Header: same layout as the cancel dialog (round close button, centered title + booking id) -->\n <div class=\"sbd-reissue__header\">\n <button type=\"button\" class=\"sbd-reissue__close\" (click)=\"closed.emit()\" aria-label=\"Close\">\n <i class=\"pi pi-times\"></i>\n </button>\n <div class=\"sbd-reissue__header-text\">\n <h3 class=\"sbd-reissue__title\">{{ title() }}</h3>\n <span class=\"sbd-reissue__sub\">Booking ID #{{ bookingId() }}</span>\n </div>\n </div>\n\n @if (store.loading()) {\n <div class=\"sbd-reissue__state\">\n <div class=\"sbd-reissue__state-icon\"><i class=\"pi pi-spin pi-spinner\"></i></div>\n <div class=\"sbd-reissue__state-text\">Checking availability\u2026</div>\n <div class=\"sbd-reissue__dots\"><span></span><span></span><span></span></div>\n </div>\n } @else if (store.error()) {\n <div class=\"sbd-reissue__state\">\n <div class=\"sbd-reissue__state-icon sbd-reissue__state-icon--warn\"><i class=\"pi pi-exclamation-triangle\"></i></div>\n <div class=\"sbd-reissue__state-text\">{{ store.error() }}</div>\n <button pButton type=\"button\" label=\"Retry\" class=\"p-button-outlined p-button-secondary\" (click)=\"store.load()\"></button>\n </div>\n } @else if (store.blocked()) {\n <!-- No leg can be reissued online: same idea as the cancel dialog's \"not eligible, escalate\" notice. -->\n <div class=\"sbd-reissue__body\">\n <app-sbd-reissue-notice tone=\"warn\" icon=\"pi pi-exclamation-triangle\">\n <strong>{{ store.blocked()?.title }}</strong><br>{{ store.blocked()?.text }}\n </app-sbd-reissue-notice>\n </div>\n <div class=\"sbd-reissue__footer\">\n <button pButton type=\"button\" label=\"Close\" class=\"p-button-outlined p-button-secondary sbd-reissue__btn\"\n (click)=\"closed.emit()\"></button>\n </div>\n } @else {\n <div class=\"sbd-reissue__body\">\n @switch (store.step()) {\n @case ('service') {\n <app-sbd-reissue-notice>Change requests are subject to availability from the airline.</app-sbd-reissue-notice>\n <app-sbd-reissue-service-step\n [upgradeAvailable]=\"store.upgradeAvailable()\"\n (chosen)=\"onServiceChosen($event)\" />\n }\n @case ('legs') {\n <app-sbd-reissue-legs-step\n [rows]=\"store.legRows()\"\n [anyAvailable]=\"store.anyAvailable()\"\n (toggled)=\"store.toggleLeg($event)\" />\n }\n @case ('edit') {\n @if (store.service() === 'change_date') {\n <app-sbd-reissue-date-step\n [rows]=\"store.dateRows()\"\n (changed)=\"store.setDate($event.legId, $event.date)\" />\n } @else {\n <app-sbd-reissue-route-step\n [rows]=\"store.routeRows()\"\n [options]=\"store.airportOptions()\"\n (airportChanged)=\"store.setAirport($event.legId, $event.side, $event.airport)\"\n (searched)=\"store.searchAirports($event)\"\n (swapped)=\"store.swapAirports($event)\" />\n }\n }\n @case ('upgrade-legs') {\n <app-sbd-reissue-legs-step\n unavailableText=\"None of these flights can be upgraded online. Handle this booking manually.\"\n [rows]=\"upgrade.legRows()\"\n [anyAvailable]=\"upgrade.anyLegUpgradable()\"\n [note]=\"store.allowSingleLegSelection() ? null : 'These flights must be upgraded together.'\"\n [error]=\"upgrade.error()\"\n (toggled)=\"upgrade.toggleLeg($event)\" />\n }\n @case ('upgrade') {\n <app-sbd-reissue-upgrade-step\n [sections]=\"upgrade.sections()\"\n [error]=\"upgrade.error()\"\n (picked)=\"upgrade.select($event.sectionId, $event.key)\" />\n }\n @case ('confirm') {\n <app-sbd-reissue-upgrade-confirm-step\n [rows]=\"upgrade.summary()\"\n [total]=\"upgrade.total()\"\n [totalLabel]=\"upgrade.totalLabel()\"\n [priceChanged]=\"upgrade.priceChanged()\"\n [booked]=\"upgrade.booked()\"\n [link]=\"upgrade.paymentLink()\"\n [error]=\"upgrade.error()\"\n (restart)=\"restartFlow()\" />\n }\n }\n </div>\n\n @if (hint(); as text) {\n <div class=\"sbd-reissue__hint\">{{ text }}</div>\n }\n\n @if (store.step() !== 'service') {\n <div class=\"sbd-reissue__footer\">\n <button pButton type=\"button\" label=\"Back\" class=\"p-button-outlined p-button-secondary sbd-reissue__btn\"\n [disabled]=\"store.step() === 'confirm' && upgrade.locked()\" (click)=\"onBack()\"></button>\n\n @switch (store.step()) {\n @case ('legs') {\n <button pButton type=\"button\" label=\"Continue\" class=\"sbd-reissue__btn\"\n [disabled]=\"!store.canContinue()\" (click)=\"store.goToEdit()\"></button>\n }\n @case ('edit') {\n <button pButton type=\"button\" icon=\"pi pi-search\" label=\"Search flights\" class=\"sbd-reissue__btn\"\n [disabled]=\"!!store.validationError()\" (click)=\"searchFlights()\"></button>\n }\n @case ('upgrade-legs') {\n <button pButton type=\"button\" label=\"Continue\" class=\"sbd-reissue__btn\" [loading]=\"upgrade.submitting()\"\n [disabled]=\"!upgrade.canContinueFromLegs()\" (click)=\"upgrade.continueFromLegs()\"></button>\n }\n @case ('upgrade') {\n <button pButton type=\"button\" label=\"Continue\" class=\"sbd-reissue__btn\" [loading]=\"upgrade.submitting()\"\n [disabled]=\"!upgrade.canContinue()\" (click)=\"upgrade.continue()\"></button>\n }\n @case ('confirm') {\n @switch (upgrade.action()) {\n @case ('done') {\n <button pButton type=\"button\" label=\"Close\" class=\"sbd-reissue__btn\" (click)=\"closed.emit()\"></button>\n }\n @case ('book') {\n <button pButton type=\"button\" label=\"Confirm upgrade\" class=\"sbd-reissue__btn\"\n [loading]=\"upgrade.submitting()\" (click)=\"upgrade.confirm()\"></button>\n }\n @case ('link') {\n <button pButton type=\"button\" icon=\"pi pi-link\" label=\"Generate payment link\" class=\"sbd-reissue__btn\"\n [loading]=\"upgrade.submitting()\" (click)=\"upgrade.confirm()\"></button>\n }\n }\n }\n }\n </div>\n }\n }\n </div>\n</p-dialog>\n", styles: [":host ::ng-deep .sbd-reissue-dialog-wrapper{border-radius:16px;box-shadow:0 24px 60px #0f172a38}:host ::ng-deep .sbd-reissue-dialog-wrapper .p-dialog-content{padding:0;border-radius:16px;overflow:visible}.sbd-reissue{font-family:inherit;position:relative}.sbd-reissue__header{display:flex;align-items:flex-start;gap:12px;padding:20px 20px 0}.sbd-reissue__close{flex-shrink:0;width:32px;height:32px;border-radius:50%;background:#f8fafc;border:none;color:#1e2e4a;display:flex;align-items:center;justify-content:center;cursor:pointer}.sbd-reissue__close .pi{font-size:13px}.sbd-reissue__close:hover{background:#e2e8f0}.sbd-reissue__header-text{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px;text-align:center;margin-right:32px}.sbd-reissue__title{font-size:19px;font-weight:800;color:#1e2e4a;margin:0}.sbd-reissue__sub{font-size:12.5px;color:#94a3b8}.sbd-reissue__body{padding:16px 20px 20px;display:flex;flex-direction:column;gap:12px}.sbd-reissue__hint{text-align:center;padding:0 20px 12px;font-size:13px;color:#94a3b8}.sbd-reissue__footer{display:flex;justify-content:flex-end;gap:10px;align-items:stretch;padding:14px 20px;border-top:1px solid #e2e8f0;background:#fff;border-radius:0 0 16px 16px}.sbd-reissue__btn{flex:1 1 0;justify-content:center}.sbd-reissue__state{padding:36px 24px 28px;display:flex;flex-direction:column;align-items:center;gap:14px;text-align:center}.sbd-reissue__state-icon{width:56px;height:56px;border-radius:50%;background:#fff7ed;border:2px solid #fed7aa;display:flex;align-items:center;justify-content:center}.sbd-reissue__state-icon .pi{font-size:24px;color:#f97316}.sbd-reissue__state-icon--warn{background:#fef2f2;border-color:#fecaca}.sbd-reissue__state-icon--warn .pi{color:#ef4444}.sbd-reissue__state-text{font-size:13px;color:#64748b;min-height:20px}.sbd-reissue__dots{display:flex;gap:6px}.sbd-reissue__dots span{width:7px;height:7px;border-radius:50%;background:#f97316;opacity:.3;animation:sbd-reissue-dot 1.2s ease-in-out infinite}.sbd-reissue__dots span:nth-child(2){animation-delay:.2s}.sbd-reissue__dots span:nth-child(3){animation-delay:.4s}@keyframes sbd-reissue-dot{0%,to{opacity:.3;transform:scale(1)}50%{opacity:1;transform:scale(1.4)}}\n"] }]
7949
+ }], ctorParameters: () => [] });
7950
+
6061
7951
  class SbdHotelContentComponent {
6062
7952
  constructor() {
6063
7953
  // Inputs
@@ -6182,6 +8072,8 @@ class SmartBookingDashboardComponent {
6182
8072
  constructor() {
6183
8073
  this.bookingService = inject(SbdBookingService);
6184
8074
  this.toast = inject(SbdToastService);
8075
+ /** The Reissue button is shown for flights only, and only when the host enabled the feature. */
8076
+ this.reissueEnabled = inject(SBD_CONFIG).reissueEnabled;
6185
8077
  this.product = signal('flight');
6186
8078
  this.bookingId = signal('');
6187
8079
  this.bookingStatus = signal('idle');
@@ -6190,6 +8082,7 @@ class SmartBookingDashboardComponent {
6190
8082
  this.hotelAutoCancelDialogOpen = signal(false);
6191
8083
  this.actionDialogOpen = signal(false);
6192
8084
  this.addNoteDialogOpen = signal(false);
8085
+ this.reissueDialogOpen = signal(false);
6193
8086
  this.selectedActionKey = signal(null);
6194
8087
  this.activeContextTab = signal('changes');
6195
8088
  this.availableActions = computed(() => (this.product() === 'flight' ? FLIGHT_ACTIONS : HOTEL_ACTIONS).filter(a => !a.hidden));
@@ -6295,7 +8188,7 @@ class SmartBookingDashboardComponent {
6295
8188
  });
6296
8189
  }
6297
8190
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SmartBookingDashboardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6298
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SmartBookingDashboardComponent, isStandalone: true, selector: "app-smart-booking-dashboard", host: { listeners: { "document:keydown": "onKeydown($event)" } }, ngImport: i0, template: "<div class=\"sbd-page\">\n\n <!-- Search bar full-width at top -->\n <div class=\"sbd-page__search\">\n <app-sbd-booking-input\n [(product)]=\"product\"\n [(bookingId)]=\"bookingId\"\n [loading]=\"bookingStatus() === 'loading'\"\n (lookup)=\"lookupBooking()\"\n />\n </div>\n\n <!-- Hotel: Use dedicated component -->\n @if (product() === 'hotel') {\n <app-sbd-hotel-content\n [bookingData]=\"bookingData()\"\n [bookingStatus]=\"bookingStatus()\"\n [bookingId]=\"bookingId()\"\n [dataVerified]=\"dataVerified()\"\n [bookingNotes]=\"bookingNotes()\"\n [bookingHistory]=\"bookingHistory()\"\n [historyGroups]=\"historyGroups()\"\n [activeContextTab]=\"activeContextTab()\"\n (cancelBooking)=\"cancelDialogOpen.set(true)\"\n (addNoteRequested)=\"addNoteDialogOpen.set(true)\"\n (contextTabChanged)=\"activeContextTab.set($event)\"\n />\n }\n\n <!-- Flight: Keep original 2-column layout -->\n @if (product() === 'flight') {\n <div class=\"sbd-page__content\">\n\n <!-- Left: booking context -->\n <aside class=\"sbd-page__aside\">\n\n <!-- Data search at the top of the sidebar -->\n @if (bookingStatus() === 'success') {\n <app-sbd-data-search [data]=\"bookingData()\" />\n <app-sbd-flight-itinerary-card [product]=\"product()\" [data]=\"bookingData()\" [verified]=\"dataVerified()\" />\n }\n\n <!-- Loading skeleton for aside -->\n @if (bookingStatus() === 'loading') {\n <div class=\"sbd-page__skeleton-card\">\n <div class=\"sbd-page__skeleton-row\" style=\"width:60%\"></div>\n <div class=\"sbd-page__skeleton-row\" style=\"width:40%\"></div>\n <div class=\"sbd-page__skeleton-row\" style=\"width:80%\"></div>\n <div class=\"sbd-page__skeleton-row\" style=\"width:50%\"></div>\n </div>\n }\n\n </aside>\n\n <!-- Right: agent workspace -->\n <main class=\"sbd-page__main\">\n\n <!-- Error state -->\n @if (bookingStatus() === 'error') {\n <div class=\"sbd-page__error\">\n <i class=\"pi pi-exclamation-circle\"></i>\n <span>Booking not found. Double-check the ID and product type, then try again.</span>\n </div>\n }\n\n <!-- Content once loaded -->\n @if (bookingStatus() === 'success') {\n\n <!-- Quick actions -->\n <div class=\"sbd-page__quick-actions\">\n <span class=\"sbd-page__quick-label\">Quick actions</span>\n <app-sbd-workflow-actions\n [bookingId]=\"bookingId()\"\n (cancelBooking)=\"cancelDialogOpen.set(true)\"\n />\n </div>\n\n <!-- Action grid -->\n <app-sbd-action-grid\n [actions]=\"availableActions()\"\n [activeKey]=\"selectedActionKey()\"\n (actionSelected)=\"openAction($event)\"\n />\n\n <!-- Context: Notes + Changes tabbed -->\n <div class=\"sbd-page__context\">\n\n <!-- Tab bar -->\n <div class=\"sbd-page__context-tabs\">\n <button class=\"sbd-page__context-tab\"\n [class.sbd-page__context-tab--active]=\"activeContextTab() === 'changes'\"\n (click)=\"activeContextTab.set('changes')\">\n <i class=\"pi pi-history\"></i>\n Changes\n @if (bookingHistory().length > 0) {\n <span class=\"sbd-page__context-pill sbd-page__context-pill--changes\">{{ bookingHistory().length }}</span>\n }\n </button>\n <button class=\"sbd-page__context-tab\"\n [class.sbd-page__context-tab--active]=\"activeContextTab() === 'notes'\"\n (click)=\"activeContextTab.set('notes')\">\n <i class=\"pi pi-comments\"></i>\n Notes\n <span class=\"sbd-page__context-pill sbd-page__context-pill--notes\">{{ bookingNotes().length }}</span>\n </button>\n @if (activeContextTab() === 'notes') {\n <button pButton label=\"Add Note\" icon=\"pi pi-plus\" class=\"p-button-sm p-button-outlined sbd-page__context-add\"\n (click)=\"addNoteDialogOpen.set(true)\">\n </button>\n }\n </div>\n\n <!-- Notes tab -->\n @if (activeContextTab() === 'notes') {\n <div class=\"sbd-page__notes-list\">\n @if (bookingNotes().length === 0) {\n <div class=\"sbd-page__context-empty\">\n <i class=\"pi pi-inbox\"></i> No notes yet\n </div>\n }\n @for (note of bookingNotes(); track $index) {\n <div class=\"sbd-page__note\">\n <div class=\"sbd-page__note-top\">\n <span class=\"sbd-page__note-badge sbd-page__note-badge--{{ note.action | lowercase }}\">\n {{ note.action }}\n </span>\n <span class=\"sbd-page__note-author\">\n {{ note.writtenBy?.firstName }} {{ note.writtenBy?.lastName }}\n </span>\n <span class=\"sbd-page__note-date\">{{ fmtNoteDate(note.date) }}</span>\n </div>\n <p class=\"sbd-page__note-msg\">{{ note.message }}</p>\n </div>\n }\n </div>\n }\n\n <!-- Changes tab -->\n @if (activeContextTab() === 'changes') {\n <div class=\"sbd-page__changes-groups\">\n @if (historyGroups().length === 0) {\n <div class=\"sbd-page__context-empty\">\n <i class=\"pi pi-check\"></i> No changes recorded\n </div>\n }\n @for (group of historyGroups(); track group.changedAt) {\n <div class=\"sbd-page__changes-group\">\n <div class=\"sbd-page__changes-meta\">\n <i class=\"pi pi-user\"></i>\n <strong>{{ group.changedBy }}</strong>\n <span class=\"sbd-page__changes-sep\">\u00B7</span>\n <span>{{ fmtDate(group.changedAt) }}</span>\n </div>\n <table class=\"sbd-page__changes-table\">\n <thead>\n <tr>\n <th>Field</th>\n <th><i class=\"pi pi-times-circle\"></i> Before</th>\n <th><i class=\"pi pi-check-circle\"></i> After</th>\n </tr>\n </thead>\n <tbody>\n @for (entry of group.entries; track entry.field) {\n <tr>\n <td class=\"sbd-page__changes-field\">{{ formatField(entry.field) }}</td>\n <td class=\"sbd-page__changes-old\">{{ entry.from }}</td>\n <td class=\"sbd-page__changes-new\">{{ entry.to }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </div>\n }\n\n </div>\n\n }\n\n <!-- Loading skeleton for main -->\n @if (bookingStatus() === 'loading') {\n <div class=\"sbd-page__skeleton-card\">\n <div class=\"sbd-page__skeleton-row\" style=\"width:40%\"></div>\n <div class=\"sbd-page__skeleton-row\" style=\"width:25%\"></div>\n </div>\n }\n\n </main>\n\n </div>\n }\n\n</div>\n\n<app-sbd-cancel-dialog\n [visible]=\"cancelDialogOpen() && product() === 'flight'\"\n [bookingId]=\"bookingId()\"\n [product]=\"product()\"\n [pnrInfo]=\"cancelPnrInfo()\"\n [bookingInfo]=\"bookingData()\"\n (closed)=\"cancelDialogOpen.set(false)\"\n (confirmed)=\"bookingStatus.set('idle'); bookingData.set(null)\"\n/>\n\n<almatar-hotel-auto-cancellation\n *ngIf=\"cancelDialogOpen() && product() === 'hotel'\"\n [selectedHotelBooking]=\"bookingData()\"\n (closed)=\"cancelDialogOpen.set(false)\"\n (cancellationSuccess)=\"bookingStatus.set('idle'); bookingData.set(null)\"\n/>\n\n<app-sbd-action-dialog\n [visible]=\"actionDialogOpen()\"\n [bookingId]=\"bookingId()\"\n [product]=\"product()\"\n [actions]=\"availableActions()\"\n [initialKey]=\"selectedActionKey()\"\n [preloadedData]=\"bookingData()\"\n (closed)=\"actionDialogOpen.set(false)\"\n/>\n\n<app-sbd-add-note-dialog\n [visible]=\"addNoteDialogOpen()\"\n [bookingId]=\"bookingId()\"\n [product]=\"product()\"\n (closed)=\"addNoteDialogOpen.set(false)\"\n/>\n", styles: [".sbd-page{display:flex;flex-direction:column;gap:20px;padding:24px;max-width:1400px;min-height:100%}.sbd-page__search{width:100%}.sbd-page__error{display:flex;align-items:center;gap:10px;padding:16px 20px;background:#fef2f2;border:1px solid rgba(239,68,68,.2);border-radius:10px;color:#ef4444;font-size:13.5px;font-weight:500}.sbd-page__error i{font-size:18px}.sbd-page__content{display:grid;grid-template-columns:320px 1fr;gap:20px;align-items:start}@media (max-width: 960px){.sbd-page__content{grid-template-columns:1fr}}.sbd-page__aside{display:flex;flex-direction:column;gap:16px;position:sticky;top:20px}.sbd-page__main{display:flex;flex-direction:column;gap:16px}.sbd-page__quick-actions{display:flex;align-items:center;gap:12px;padding:12px 16px;background:#fff;border:1px solid #e2e8f0;border-radius:10px;flex-wrap:wrap}.sbd-page__quick-label{font-size:11.5px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:#94a3b8;flex-shrink:0}.sbd-page__context{background:#fff;border:1px solid #e2e8f0;border-radius:12px;overflow:hidden}.sbd-page__context-tabs{display:flex;align-items:center;border-bottom:1px solid #e2e8f0;background:#f8fafc}.sbd-page__context-add{margin-left:auto;margin-right:10px}.sbd-page__context-tab{display:flex;align-items:center;gap:7px;padding:11px 20px;font-size:13px;font-weight:600;color:#64748b;background:transparent;border:none;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px;transition:color .15s,border-color .15s}.sbd-page__context-tab .pi{font-size:13px}.sbd-page__context-tab:hover{color:#334155}.sbd-page__context-tab--active{color:#1e2e4a;border-bottom-color:var(--sbd-primary, #3b82f6);background:#fff}.sbd-page__context-pill{font-size:10.5px;font-weight:700;padding:1px 7px;border-radius:10px}.sbd-page__context-pill--notes{background:#e0e7ff;color:#4338ca}.sbd-page__context-pill--changes{background:#fef3c7;color:#b45309}.sbd-page__context-empty{display:flex;align-items:center;gap:8px;padding:24px 20px;font-size:13px;color:#94a3b8}.sbd-page__context-empty .pi{font-size:16px}.sbd-page__notes-list{display:flex;flex-direction:column}.sbd-page__note{padding:12px 16px;border-bottom:1px solid #f1f5f9;display:flex;flex-direction:column;gap:6px}.sbd-page__note:last-child{border-bottom:none}.sbd-page__note-top{display:flex;align-items:center;gap:8px}.sbd-page__note-badge{font-size:10px;font-weight:800;letter-spacing:.05em;padding:2px 8px;border-radius:10px;text-transform:uppercase;flex-shrink:0}.sbd-page__note-badge--general{background:#dbeafe;color:#1d4ed8}.sbd-page__note-badge--remark{background:#fef3c7;color:#b45309}.sbd-page__note-badge--feedback{background:#f3e8ff;color:#7e22ce}.sbd-page__note-author{font-size:12px;font-weight:700;color:#1e2e4a}.sbd-page__note-date{margin-left:auto;font-size:11px;color:#94a3b8;white-space:nowrap}.sbd-page__note-msg{margin:0;font-size:13px;color:#334155;line-height:1.55}.sbd-page__changes-groups{display:flex;flex-direction:column}.sbd-page__changes-group{border-bottom:1px solid #f1f5f9}.sbd-page__changes-group:last-child{border-bottom:none}.sbd-page__changes-meta{display:flex;align-items:center;gap:6px;padding:8px 16px;background:#fafafa;border-bottom:1px solid #f1f5f9;font-size:11.5px;color:#64748b}.sbd-page__changes-meta strong{font-weight:700;color:#334155}.sbd-page__changes-meta .pi{font-size:11px;color:#94a3b8}.sbd-page__changes-sep{color:#cbd5e1}.sbd-page__changes-table{width:100%;border-collapse:collapse;font-size:12.5px}.sbd-page__changes-table thead tr{background:#f8fafc;border-bottom:1px solid #e2e8f0}.sbd-page__changes-table thead th{padding:7px 14px;text-align:left;font-size:10.5px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:#64748b;white-space:nowrap}.sbd-page__changes-table thead th .pi{margin-right:4px;font-size:10px}.sbd-page__changes-table thead th:nth-child(2){color:#dc2626}.sbd-page__changes-table thead th:nth-child(3){color:#16a34a}.sbd-page__changes-table tbody tr{border-bottom:1px solid #f1f5f9}.sbd-page__changes-table tbody tr:last-child{border-bottom:none}.sbd-page__changes-table tbody tr:hover{background:#f8fafc}.sbd-page__changes-table tbody td{padding:8px 14px;vertical-align:top;word-break:break-word}.sbd-page__changes-field{font-size:12px;font-weight:600;color:#475569;width:28%}.sbd-page__changes-old{color:#dc2626;font-weight:500;text-decoration:line-through;opacity:.8;width:36%}.sbd-page__changes-new{color:#16a34a;font-weight:700;width:36%}.sbd-page__content--loading{opacity:.6;pointer-events:none}.sbd-page__skeleton-card{background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:20px;display:flex;flex-direction:column;gap:12px}.sbd-page__skeleton-row{height:13px;background:#f1f5f9;border-radius:6px;animation:sbd-shimmer 1.4s ease-in-out infinite}.sbd-page__skeleton-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:10px;margin-top:8px}.sbd-page__skeleton-chip{height:56px;background:#f1f5f9;border-radius:8px;animation:sbd-shimmer 1.4s ease-in-out infinite}@keyframes sbd-shimmer{0%,to{opacity:1}50%{opacity:.45}}@keyframes sbd-fadein{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}\n"], dependencies: [{ kind: "component", type: SbdBookingInputComponent, selector: "app-sbd-booking-input", inputs: ["loading", "product", "bookingId"], outputs: ["productChange", "bookingIdChange", "lookup"] }, { kind: "component", type: SbdWorkflowActionsComponent, selector: "app-sbd-workflow-actions", inputs: ["bookingId"], outputs: ["cancelBooking"] }, { kind: "component", type: SbdActionGridComponent, selector: "app-sbd-action-grid", inputs: ["actions", "activeKey", "loadingKey"], outputs: ["actionSelected"] }, { kind: "component", type: SbdCancelDialogComponent, selector: "app-sbd-cancel-dialog", inputs: ["visible", "bookingId", "product", "pnrInfo", "bookingInfo"], outputs: ["closed", "confirmed"] }, { kind: "component", type: AlmatarHotelAutoCancellationComponent, selector: "almatar-hotel-auto-cancellation", inputs: ["selectedHotelBooking"], outputs: ["closed", "cancellationSuccess", "nonRefundableSubmitSuccess", "chatWithAgent"] }, { kind: "component", type: SbdFlightItineraryCardComponent, selector: "app-sbd-flight-itinerary-card", inputs: ["data", "product", "verified"] }, { kind: "component", type: SbdDataSearchComponent, selector: "app-sbd-data-search", inputs: ["data"] }, { kind: "component", type: SbdActionDialogComponent, selector: "app-sbd-action-dialog", inputs: ["visible", "bookingId", "product", "actions", "initialKey", "preloadedData"], outputs: ["closed"] }, { kind: "component", type: SbdAddNoteDialogComponent, selector: "app-sbd-add-note-dialog", inputs: ["visible", "bookingId", "product"], outputs: ["closed"] }, { kind: "component", type: SbdHotelContentComponent, selector: "app-sbd-hotel-content", inputs: ["bookingData", "bookingStatus", "bookingId", "dataVerified", "bookingNotes", "bookingHistory", "historyGroups", "activeContextTab"], outputs: ["cancelBooking", "addNoteRequested", "contextTabChanged"] }, { kind: "pipe", type: LowerCasePipe, name: "lowercase" }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i2$1.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "label", "icon", "loading"] }] }); }
8191
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SmartBookingDashboardComponent, isStandalone: true, selector: "app-smart-booking-dashboard", host: { listeners: { "document:keydown": "onKeydown($event)" } }, ngImport: i0, template: "<div class=\"sbd-page\">\n\n <!-- Search bar full-width at top -->\n <div class=\"sbd-page__search\">\n <app-sbd-booking-input\n [(product)]=\"product\"\n [(bookingId)]=\"bookingId\"\n [loading]=\"bookingStatus() === 'loading'\"\n (lookup)=\"lookupBooking()\"\n />\n </div>\n\n <!-- Hotel: Use dedicated component -->\n @if (product() === 'hotel') {\n <app-sbd-hotel-content\n [bookingData]=\"bookingData()\"\n [bookingStatus]=\"bookingStatus()\"\n [bookingId]=\"bookingId()\"\n [dataVerified]=\"dataVerified()\"\n [bookingNotes]=\"bookingNotes()\"\n [bookingHistory]=\"bookingHistory()\"\n [historyGroups]=\"historyGroups()\"\n [activeContextTab]=\"activeContextTab()\"\n (cancelBooking)=\"cancelDialogOpen.set(true)\"\n (addNoteRequested)=\"addNoteDialogOpen.set(true)\"\n (contextTabChanged)=\"activeContextTab.set($event)\"\n />\n }\n\n <!-- Flight: Keep original 2-column layout -->\n @if (product() === 'flight') {\n <div class=\"sbd-page__content\">\n\n <!-- Left: booking context -->\n <aside class=\"sbd-page__aside\">\n\n <!-- Data search at the top of the sidebar -->\n @if (bookingStatus() === 'success') {\n <app-sbd-data-search [data]=\"bookingData()\" />\n <app-sbd-flight-itinerary-card [product]=\"product()\" [data]=\"bookingData()\" [verified]=\"dataVerified()\" />\n }\n\n <!-- Loading skeleton for aside -->\n @if (bookingStatus() === 'loading') {\n <div class=\"sbd-page__skeleton-card\">\n <div class=\"sbd-page__skeleton-row\" style=\"width:60%\"></div>\n <div class=\"sbd-page__skeleton-row\" style=\"width:40%\"></div>\n <div class=\"sbd-page__skeleton-row\" style=\"width:80%\"></div>\n <div class=\"sbd-page__skeleton-row\" style=\"width:50%\"></div>\n </div>\n }\n\n </aside>\n\n <!-- Right: agent workspace -->\n <main class=\"sbd-page__main\">\n\n <!-- Error state -->\n @if (bookingStatus() === 'error') {\n <div class=\"sbd-page__error\">\n <i class=\"pi pi-exclamation-circle\"></i>\n <span>Booking not found. Double-check the ID and product type, then try again.</span>\n </div>\n }\n\n <!-- Content once loaded -->\n @if (bookingStatus() === 'success') {\n\n <!-- Quick actions -->\n <div class=\"sbd-page__quick-actions\">\n <span class=\"sbd-page__quick-label\">Quick actions</span>\n <app-sbd-workflow-actions\n [bookingId]=\"bookingId()\"\n [showReissue]=\"reissueEnabled && product() === 'flight'\"\n (cancelBooking)=\"cancelDialogOpen.set(true)\"\n (reissue)=\"reissueDialogOpen.set(true)\"\n />\n </div>\n\n <!-- Action grid -->\n <app-sbd-action-grid\n [actions]=\"availableActions()\"\n [activeKey]=\"selectedActionKey()\"\n (actionSelected)=\"openAction($event)\"\n />\n\n <!-- Context: Notes + Changes tabbed -->\n <div class=\"sbd-page__context\">\n\n <!-- Tab bar -->\n <div class=\"sbd-page__context-tabs\">\n <button class=\"sbd-page__context-tab\"\n [class.sbd-page__context-tab--active]=\"activeContextTab() === 'changes'\"\n (click)=\"activeContextTab.set('changes')\">\n <i class=\"pi pi-history\"></i>\n Changes\n @if (bookingHistory().length > 0) {\n <span class=\"sbd-page__context-pill sbd-page__context-pill--changes\">{{ bookingHistory().length }}</span>\n }\n </button>\n <button class=\"sbd-page__context-tab\"\n [class.sbd-page__context-tab--active]=\"activeContextTab() === 'notes'\"\n (click)=\"activeContextTab.set('notes')\">\n <i class=\"pi pi-comments\"></i>\n Notes\n <span class=\"sbd-page__context-pill sbd-page__context-pill--notes\">{{ bookingNotes().length }}</span>\n </button>\n @if (activeContextTab() === 'notes') {\n <button pButton label=\"Add Note\" icon=\"pi pi-plus\" class=\"p-button-sm p-button-outlined sbd-page__context-add\"\n (click)=\"addNoteDialogOpen.set(true)\">\n </button>\n }\n </div>\n\n <!-- Notes tab -->\n @if (activeContextTab() === 'notes') {\n <div class=\"sbd-page__notes-list\">\n @if (bookingNotes().length === 0) {\n <div class=\"sbd-page__context-empty\">\n <i class=\"pi pi-inbox\"></i> No notes yet\n </div>\n }\n @for (note of bookingNotes(); track $index) {\n <div class=\"sbd-page__note\">\n <div class=\"sbd-page__note-top\">\n <span class=\"sbd-page__note-badge sbd-page__note-badge--{{ note.action | lowercase }}\">\n {{ note.action }}\n </span>\n <span class=\"sbd-page__note-author\">\n {{ note.writtenBy?.firstName }} {{ note.writtenBy?.lastName }}\n </span>\n <span class=\"sbd-page__note-date\">{{ fmtNoteDate(note.date) }}</span>\n </div>\n <p class=\"sbd-page__note-msg\">{{ note.message }}</p>\n </div>\n }\n </div>\n }\n\n <!-- Changes tab -->\n @if (activeContextTab() === 'changes') {\n <div class=\"sbd-page__changes-groups\">\n @if (historyGroups().length === 0) {\n <div class=\"sbd-page__context-empty\">\n <i class=\"pi pi-check\"></i> No changes recorded\n </div>\n }\n @for (group of historyGroups(); track group.changedAt) {\n <div class=\"sbd-page__changes-group\">\n <div class=\"sbd-page__changes-meta\">\n <i class=\"pi pi-user\"></i>\n <strong>{{ group.changedBy }}</strong>\n <span class=\"sbd-page__changes-sep\">\u00B7</span>\n <span>{{ fmtDate(group.changedAt) }}</span>\n </div>\n <table class=\"sbd-page__changes-table\">\n <thead>\n <tr>\n <th>Field</th>\n <th><i class=\"pi pi-times-circle\"></i> Before</th>\n <th><i class=\"pi pi-check-circle\"></i> After</th>\n </tr>\n </thead>\n <tbody>\n @for (entry of group.entries; track entry.field) {\n <tr>\n <td class=\"sbd-page__changes-field\">{{ formatField(entry.field) }}</td>\n <td class=\"sbd-page__changes-old\">{{ entry.from }}</td>\n <td class=\"sbd-page__changes-new\">{{ entry.to }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </div>\n }\n\n </div>\n\n }\n\n <!-- Loading skeleton for main -->\n @if (bookingStatus() === 'loading') {\n <div class=\"sbd-page__skeleton-card\">\n <div class=\"sbd-page__skeleton-row\" style=\"width:40%\"></div>\n <div class=\"sbd-page__skeleton-row\" style=\"width:25%\"></div>\n </div>\n }\n\n </main>\n\n </div>\n }\n\n</div>\n\n<app-sbd-cancel-dialog\n [visible]=\"cancelDialogOpen() && product() === 'flight'\"\n [bookingId]=\"bookingId()\"\n [product]=\"product()\"\n [pnrInfo]=\"cancelPnrInfo()\"\n [bookingInfo]=\"bookingData()\"\n (closed)=\"cancelDialogOpen.set(false)\"\n (confirmed)=\"bookingStatus.set('idle'); bookingData.set(null)\"\n/>\n\n<almatar-hotel-auto-cancellation\n *ngIf=\"cancelDialogOpen() && product() === 'hotel'\"\n [selectedHotelBooking]=\"bookingData()\"\n (closed)=\"cancelDialogOpen.set(false)\"\n (cancellationSuccess)=\"bookingStatus.set('idle'); bookingData.set(null)\"\n/>\n\n<app-sbd-action-dialog\n [visible]=\"actionDialogOpen()\"\n [bookingId]=\"bookingId()\"\n [product]=\"product()\"\n [actions]=\"availableActions()\"\n [initialKey]=\"selectedActionKey()\"\n [preloadedData]=\"bookingData()\"\n (closed)=\"actionDialogOpen.set(false)\"\n/>\n\n<app-sbd-add-note-dialog\n [visible]=\"addNoteDialogOpen()\"\n [bookingId]=\"bookingId()\"\n [product]=\"product()\"\n (closed)=\"addNoteDialogOpen.set(false)\"\n/>\n\n<app-sbd-reissue-dialog\n [visible]=\"reissueDialogOpen()\"\n [bookingId]=\"bookingId()\"\n [bookingData]=\"bookingData()\"\n (closed)=\"reissueDialogOpen.set(false)\"\n/>\n", styles: [".sbd-page{display:flex;flex-direction:column;gap:20px;padding:24px;max-width:1400px;min-height:100%}.sbd-page__search{width:100%}.sbd-page__error{display:flex;align-items:center;gap:10px;padding:16px 20px;background:#fef2f2;border:1px solid rgba(239,68,68,.2);border-radius:10px;color:#ef4444;font-size:13.5px;font-weight:500}.sbd-page__error i{font-size:18px}.sbd-page__content{display:grid;grid-template-columns:320px 1fr;gap:20px;align-items:start}@media (max-width: 960px){.sbd-page__content{grid-template-columns:1fr}}.sbd-page__aside{display:flex;flex-direction:column;gap:16px;position:sticky;top:20px}.sbd-page__main{display:flex;flex-direction:column;gap:16px}.sbd-page__quick-actions{display:flex;align-items:center;gap:12px;padding:12px 16px;background:#fff;border:1px solid #e2e8f0;border-radius:10px;flex-wrap:wrap}.sbd-page__quick-label{font-size:11.5px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:#94a3b8;flex-shrink:0}.sbd-page__context{background:#fff;border:1px solid #e2e8f0;border-radius:12px;overflow:hidden}.sbd-page__context-tabs{display:flex;align-items:center;border-bottom:1px solid #e2e8f0;background:#f8fafc}.sbd-page__context-add{margin-left:auto;margin-right:10px}.sbd-page__context-tab{display:flex;align-items:center;gap:7px;padding:11px 20px;font-size:13px;font-weight:600;color:#64748b;background:transparent;border:none;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px;transition:color .15s,border-color .15s}.sbd-page__context-tab .pi{font-size:13px}.sbd-page__context-tab:hover{color:#334155}.sbd-page__context-tab--active{color:#1e2e4a;border-bottom-color:var(--sbd-primary, #3b82f6);background:#fff}.sbd-page__context-pill{font-size:10.5px;font-weight:700;padding:1px 7px;border-radius:10px}.sbd-page__context-pill--notes{background:#e0e7ff;color:#4338ca}.sbd-page__context-pill--changes{background:#fef3c7;color:#b45309}.sbd-page__context-empty{display:flex;align-items:center;gap:8px;padding:24px 20px;font-size:13px;color:#94a3b8}.sbd-page__context-empty .pi{font-size:16px}.sbd-page__notes-list{display:flex;flex-direction:column}.sbd-page__note{padding:12px 16px;border-bottom:1px solid #f1f5f9;display:flex;flex-direction:column;gap:6px}.sbd-page__note:last-child{border-bottom:none}.sbd-page__note-top{display:flex;align-items:center;gap:8px}.sbd-page__note-badge{font-size:10px;font-weight:800;letter-spacing:.05em;padding:2px 8px;border-radius:10px;text-transform:uppercase;flex-shrink:0}.sbd-page__note-badge--general{background:#dbeafe;color:#1d4ed8}.sbd-page__note-badge--remark{background:#fef3c7;color:#b45309}.sbd-page__note-badge--feedback{background:#f3e8ff;color:#7e22ce}.sbd-page__note-author{font-size:12px;font-weight:700;color:#1e2e4a}.sbd-page__note-date{margin-left:auto;font-size:11px;color:#94a3b8;white-space:nowrap}.sbd-page__note-msg{margin:0;font-size:13px;color:#334155;line-height:1.55}.sbd-page__changes-groups{display:flex;flex-direction:column}.sbd-page__changes-group{border-bottom:1px solid #f1f5f9}.sbd-page__changes-group:last-child{border-bottom:none}.sbd-page__changes-meta{display:flex;align-items:center;gap:6px;padding:8px 16px;background:#fafafa;border-bottom:1px solid #f1f5f9;font-size:11.5px;color:#64748b}.sbd-page__changes-meta strong{font-weight:700;color:#334155}.sbd-page__changes-meta .pi{font-size:11px;color:#94a3b8}.sbd-page__changes-sep{color:#cbd5e1}.sbd-page__changes-table{width:100%;border-collapse:collapse;font-size:12.5px}.sbd-page__changes-table thead tr{background:#f8fafc;border-bottom:1px solid #e2e8f0}.sbd-page__changes-table thead th{padding:7px 14px;text-align:left;font-size:10.5px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:#64748b;white-space:nowrap}.sbd-page__changes-table thead th .pi{margin-right:4px;font-size:10px}.sbd-page__changes-table thead th:nth-child(2){color:#dc2626}.sbd-page__changes-table thead th:nth-child(3){color:#16a34a}.sbd-page__changes-table tbody tr{border-bottom:1px solid #f1f5f9}.sbd-page__changes-table tbody tr:last-child{border-bottom:none}.sbd-page__changes-table tbody tr:hover{background:#f8fafc}.sbd-page__changes-table tbody td{padding:8px 14px;vertical-align:top;word-break:break-word}.sbd-page__changes-field{font-size:12px;font-weight:600;color:#475569;width:28%}.sbd-page__changes-old{color:#dc2626;font-weight:500;text-decoration:line-through;opacity:.8;width:36%}.sbd-page__changes-new{color:#16a34a;font-weight:700;width:36%}.sbd-page__content--loading{opacity:.6;pointer-events:none}.sbd-page__skeleton-card{background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:20px;display:flex;flex-direction:column;gap:12px}.sbd-page__skeleton-row{height:13px;background:#f1f5f9;border-radius:6px;animation:sbd-shimmer 1.4s ease-in-out infinite}.sbd-page__skeleton-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:10px;margin-top:8px}.sbd-page__skeleton-chip{height:56px;background:#f1f5f9;border-radius:8px;animation:sbd-shimmer 1.4s ease-in-out infinite}@keyframes sbd-shimmer{0%,to{opacity:1}50%{opacity:.45}}@keyframes sbd-fadein{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}\n"], dependencies: [{ kind: "component", type: SbdBookingInputComponent, selector: "app-sbd-booking-input", inputs: ["loading", "product", "bookingId"], outputs: ["productChange", "bookingIdChange", "lookup"] }, { kind: "component", type: SbdWorkflowActionsComponent, selector: "app-sbd-workflow-actions", inputs: ["bookingId", "showReissue"], outputs: ["cancelBooking", "reissue"] }, { kind: "component", type: SbdActionGridComponent, selector: "app-sbd-action-grid", inputs: ["actions", "activeKey", "loadingKey"], outputs: ["actionSelected"] }, { kind: "component", type: SbdCancelDialogComponent, selector: "app-sbd-cancel-dialog", inputs: ["visible", "bookingId", "product", "pnrInfo", "bookingInfo"], outputs: ["closed", "confirmed"] }, { kind: "component", type: AlmatarHotelAutoCancellationComponent, selector: "almatar-hotel-auto-cancellation", inputs: ["selectedHotelBooking"], outputs: ["closed", "cancellationSuccess", "nonRefundableSubmitSuccess", "chatWithAgent"] }, { kind: "component", type: SbdFlightItineraryCardComponent, selector: "app-sbd-flight-itinerary-card", inputs: ["data", "product", "verified"] }, { kind: "component", type: SbdDataSearchComponent, selector: "app-sbd-data-search", inputs: ["data"] }, { kind: "component", type: SbdActionDialogComponent, selector: "app-sbd-action-dialog", inputs: ["visible", "bookingId", "product", "actions", "initialKey", "preloadedData"], outputs: ["closed"] }, { kind: "component", type: SbdAddNoteDialogComponent, selector: "app-sbd-add-note-dialog", inputs: ["visible", "bookingId", "product"], outputs: ["closed"] }, { kind: "component", type: SbdReissueDialogComponent, selector: "app-sbd-reissue-dialog", inputs: ["visible", "bookingId", "bookingData"], outputs: ["closed"] }, { kind: "component", type: SbdHotelContentComponent, selector: "app-sbd-hotel-content", inputs: ["bookingData", "bookingStatus", "bookingId", "dataVerified", "bookingNotes", "bookingHistory", "historyGroups", "activeContextTab"], outputs: ["cancelBooking", "addNoteRequested", "contextTabChanged"] }, { kind: "pipe", type: LowerCasePipe, name: "lowercase" }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i2$1.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "label", "icon", "loading"] }] }); }
6299
8192
  }
6300
8193
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SmartBookingDashboardComponent, decorators: [{
6301
8194
  type: Component,
@@ -6310,11 +8203,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
6310
8203
  SbdDataSearchComponent,
6311
8204
  SbdActionDialogComponent,
6312
8205
  SbdAddNoteDialogComponent,
8206
+ SbdReissueDialogComponent,
6313
8207
  SbdHotelContentComponent,
6314
8208
  LowerCasePipe,
6315
8209
  NgIf,
6316
8210
  ButtonModule,
6317
- ], template: "<div class=\"sbd-page\">\n\n <!-- Search bar full-width at top -->\n <div class=\"sbd-page__search\">\n <app-sbd-booking-input\n [(product)]=\"product\"\n [(bookingId)]=\"bookingId\"\n [loading]=\"bookingStatus() === 'loading'\"\n (lookup)=\"lookupBooking()\"\n />\n </div>\n\n <!-- Hotel: Use dedicated component -->\n @if (product() === 'hotel') {\n <app-sbd-hotel-content\n [bookingData]=\"bookingData()\"\n [bookingStatus]=\"bookingStatus()\"\n [bookingId]=\"bookingId()\"\n [dataVerified]=\"dataVerified()\"\n [bookingNotes]=\"bookingNotes()\"\n [bookingHistory]=\"bookingHistory()\"\n [historyGroups]=\"historyGroups()\"\n [activeContextTab]=\"activeContextTab()\"\n (cancelBooking)=\"cancelDialogOpen.set(true)\"\n (addNoteRequested)=\"addNoteDialogOpen.set(true)\"\n (contextTabChanged)=\"activeContextTab.set($event)\"\n />\n }\n\n <!-- Flight: Keep original 2-column layout -->\n @if (product() === 'flight') {\n <div class=\"sbd-page__content\">\n\n <!-- Left: booking context -->\n <aside class=\"sbd-page__aside\">\n\n <!-- Data search at the top of the sidebar -->\n @if (bookingStatus() === 'success') {\n <app-sbd-data-search [data]=\"bookingData()\" />\n <app-sbd-flight-itinerary-card [product]=\"product()\" [data]=\"bookingData()\" [verified]=\"dataVerified()\" />\n }\n\n <!-- Loading skeleton for aside -->\n @if (bookingStatus() === 'loading') {\n <div class=\"sbd-page__skeleton-card\">\n <div class=\"sbd-page__skeleton-row\" style=\"width:60%\"></div>\n <div class=\"sbd-page__skeleton-row\" style=\"width:40%\"></div>\n <div class=\"sbd-page__skeleton-row\" style=\"width:80%\"></div>\n <div class=\"sbd-page__skeleton-row\" style=\"width:50%\"></div>\n </div>\n }\n\n </aside>\n\n <!-- Right: agent workspace -->\n <main class=\"sbd-page__main\">\n\n <!-- Error state -->\n @if (bookingStatus() === 'error') {\n <div class=\"sbd-page__error\">\n <i class=\"pi pi-exclamation-circle\"></i>\n <span>Booking not found. Double-check the ID and product type, then try again.</span>\n </div>\n }\n\n <!-- Content once loaded -->\n @if (bookingStatus() === 'success') {\n\n <!-- Quick actions -->\n <div class=\"sbd-page__quick-actions\">\n <span class=\"sbd-page__quick-label\">Quick actions</span>\n <app-sbd-workflow-actions\n [bookingId]=\"bookingId()\"\n (cancelBooking)=\"cancelDialogOpen.set(true)\"\n />\n </div>\n\n <!-- Action grid -->\n <app-sbd-action-grid\n [actions]=\"availableActions()\"\n [activeKey]=\"selectedActionKey()\"\n (actionSelected)=\"openAction($event)\"\n />\n\n <!-- Context: Notes + Changes tabbed -->\n <div class=\"sbd-page__context\">\n\n <!-- Tab bar -->\n <div class=\"sbd-page__context-tabs\">\n <button class=\"sbd-page__context-tab\"\n [class.sbd-page__context-tab--active]=\"activeContextTab() === 'changes'\"\n (click)=\"activeContextTab.set('changes')\">\n <i class=\"pi pi-history\"></i>\n Changes\n @if (bookingHistory().length > 0) {\n <span class=\"sbd-page__context-pill sbd-page__context-pill--changes\">{{ bookingHistory().length }}</span>\n }\n </button>\n <button class=\"sbd-page__context-tab\"\n [class.sbd-page__context-tab--active]=\"activeContextTab() === 'notes'\"\n (click)=\"activeContextTab.set('notes')\">\n <i class=\"pi pi-comments\"></i>\n Notes\n <span class=\"sbd-page__context-pill sbd-page__context-pill--notes\">{{ bookingNotes().length }}</span>\n </button>\n @if (activeContextTab() === 'notes') {\n <button pButton label=\"Add Note\" icon=\"pi pi-plus\" class=\"p-button-sm p-button-outlined sbd-page__context-add\"\n (click)=\"addNoteDialogOpen.set(true)\">\n </button>\n }\n </div>\n\n <!-- Notes tab -->\n @if (activeContextTab() === 'notes') {\n <div class=\"sbd-page__notes-list\">\n @if (bookingNotes().length === 0) {\n <div class=\"sbd-page__context-empty\">\n <i class=\"pi pi-inbox\"></i> No notes yet\n </div>\n }\n @for (note of bookingNotes(); track $index) {\n <div class=\"sbd-page__note\">\n <div class=\"sbd-page__note-top\">\n <span class=\"sbd-page__note-badge sbd-page__note-badge--{{ note.action | lowercase }}\">\n {{ note.action }}\n </span>\n <span class=\"sbd-page__note-author\">\n {{ note.writtenBy?.firstName }} {{ note.writtenBy?.lastName }}\n </span>\n <span class=\"sbd-page__note-date\">{{ fmtNoteDate(note.date) }}</span>\n </div>\n <p class=\"sbd-page__note-msg\">{{ note.message }}</p>\n </div>\n }\n </div>\n }\n\n <!-- Changes tab -->\n @if (activeContextTab() === 'changes') {\n <div class=\"sbd-page__changes-groups\">\n @if (historyGroups().length === 0) {\n <div class=\"sbd-page__context-empty\">\n <i class=\"pi pi-check\"></i> No changes recorded\n </div>\n }\n @for (group of historyGroups(); track group.changedAt) {\n <div class=\"sbd-page__changes-group\">\n <div class=\"sbd-page__changes-meta\">\n <i class=\"pi pi-user\"></i>\n <strong>{{ group.changedBy }}</strong>\n <span class=\"sbd-page__changes-sep\">\u00B7</span>\n <span>{{ fmtDate(group.changedAt) }}</span>\n </div>\n <table class=\"sbd-page__changes-table\">\n <thead>\n <tr>\n <th>Field</th>\n <th><i class=\"pi pi-times-circle\"></i> Before</th>\n <th><i class=\"pi pi-check-circle\"></i> After</th>\n </tr>\n </thead>\n <tbody>\n @for (entry of group.entries; track entry.field) {\n <tr>\n <td class=\"sbd-page__changes-field\">{{ formatField(entry.field) }}</td>\n <td class=\"sbd-page__changes-old\">{{ entry.from }}</td>\n <td class=\"sbd-page__changes-new\">{{ entry.to }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </div>\n }\n\n </div>\n\n }\n\n <!-- Loading skeleton for main -->\n @if (bookingStatus() === 'loading') {\n <div class=\"sbd-page__skeleton-card\">\n <div class=\"sbd-page__skeleton-row\" style=\"width:40%\"></div>\n <div class=\"sbd-page__skeleton-row\" style=\"width:25%\"></div>\n </div>\n }\n\n </main>\n\n </div>\n }\n\n</div>\n\n<app-sbd-cancel-dialog\n [visible]=\"cancelDialogOpen() && product() === 'flight'\"\n [bookingId]=\"bookingId()\"\n [product]=\"product()\"\n [pnrInfo]=\"cancelPnrInfo()\"\n [bookingInfo]=\"bookingData()\"\n (closed)=\"cancelDialogOpen.set(false)\"\n (confirmed)=\"bookingStatus.set('idle'); bookingData.set(null)\"\n/>\n\n<almatar-hotel-auto-cancellation\n *ngIf=\"cancelDialogOpen() && product() === 'hotel'\"\n [selectedHotelBooking]=\"bookingData()\"\n (closed)=\"cancelDialogOpen.set(false)\"\n (cancellationSuccess)=\"bookingStatus.set('idle'); bookingData.set(null)\"\n/>\n\n<app-sbd-action-dialog\n [visible]=\"actionDialogOpen()\"\n [bookingId]=\"bookingId()\"\n [product]=\"product()\"\n [actions]=\"availableActions()\"\n [initialKey]=\"selectedActionKey()\"\n [preloadedData]=\"bookingData()\"\n (closed)=\"actionDialogOpen.set(false)\"\n/>\n\n<app-sbd-add-note-dialog\n [visible]=\"addNoteDialogOpen()\"\n [bookingId]=\"bookingId()\"\n [product]=\"product()\"\n (closed)=\"addNoteDialogOpen.set(false)\"\n/>\n", styles: [".sbd-page{display:flex;flex-direction:column;gap:20px;padding:24px;max-width:1400px;min-height:100%}.sbd-page__search{width:100%}.sbd-page__error{display:flex;align-items:center;gap:10px;padding:16px 20px;background:#fef2f2;border:1px solid rgba(239,68,68,.2);border-radius:10px;color:#ef4444;font-size:13.5px;font-weight:500}.sbd-page__error i{font-size:18px}.sbd-page__content{display:grid;grid-template-columns:320px 1fr;gap:20px;align-items:start}@media (max-width: 960px){.sbd-page__content{grid-template-columns:1fr}}.sbd-page__aside{display:flex;flex-direction:column;gap:16px;position:sticky;top:20px}.sbd-page__main{display:flex;flex-direction:column;gap:16px}.sbd-page__quick-actions{display:flex;align-items:center;gap:12px;padding:12px 16px;background:#fff;border:1px solid #e2e8f0;border-radius:10px;flex-wrap:wrap}.sbd-page__quick-label{font-size:11.5px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:#94a3b8;flex-shrink:0}.sbd-page__context{background:#fff;border:1px solid #e2e8f0;border-radius:12px;overflow:hidden}.sbd-page__context-tabs{display:flex;align-items:center;border-bottom:1px solid #e2e8f0;background:#f8fafc}.sbd-page__context-add{margin-left:auto;margin-right:10px}.sbd-page__context-tab{display:flex;align-items:center;gap:7px;padding:11px 20px;font-size:13px;font-weight:600;color:#64748b;background:transparent;border:none;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px;transition:color .15s,border-color .15s}.sbd-page__context-tab .pi{font-size:13px}.sbd-page__context-tab:hover{color:#334155}.sbd-page__context-tab--active{color:#1e2e4a;border-bottom-color:var(--sbd-primary, #3b82f6);background:#fff}.sbd-page__context-pill{font-size:10.5px;font-weight:700;padding:1px 7px;border-radius:10px}.sbd-page__context-pill--notes{background:#e0e7ff;color:#4338ca}.sbd-page__context-pill--changes{background:#fef3c7;color:#b45309}.sbd-page__context-empty{display:flex;align-items:center;gap:8px;padding:24px 20px;font-size:13px;color:#94a3b8}.sbd-page__context-empty .pi{font-size:16px}.sbd-page__notes-list{display:flex;flex-direction:column}.sbd-page__note{padding:12px 16px;border-bottom:1px solid #f1f5f9;display:flex;flex-direction:column;gap:6px}.sbd-page__note:last-child{border-bottom:none}.sbd-page__note-top{display:flex;align-items:center;gap:8px}.sbd-page__note-badge{font-size:10px;font-weight:800;letter-spacing:.05em;padding:2px 8px;border-radius:10px;text-transform:uppercase;flex-shrink:0}.sbd-page__note-badge--general{background:#dbeafe;color:#1d4ed8}.sbd-page__note-badge--remark{background:#fef3c7;color:#b45309}.sbd-page__note-badge--feedback{background:#f3e8ff;color:#7e22ce}.sbd-page__note-author{font-size:12px;font-weight:700;color:#1e2e4a}.sbd-page__note-date{margin-left:auto;font-size:11px;color:#94a3b8;white-space:nowrap}.sbd-page__note-msg{margin:0;font-size:13px;color:#334155;line-height:1.55}.sbd-page__changes-groups{display:flex;flex-direction:column}.sbd-page__changes-group{border-bottom:1px solid #f1f5f9}.sbd-page__changes-group:last-child{border-bottom:none}.sbd-page__changes-meta{display:flex;align-items:center;gap:6px;padding:8px 16px;background:#fafafa;border-bottom:1px solid #f1f5f9;font-size:11.5px;color:#64748b}.sbd-page__changes-meta strong{font-weight:700;color:#334155}.sbd-page__changes-meta .pi{font-size:11px;color:#94a3b8}.sbd-page__changes-sep{color:#cbd5e1}.sbd-page__changes-table{width:100%;border-collapse:collapse;font-size:12.5px}.sbd-page__changes-table thead tr{background:#f8fafc;border-bottom:1px solid #e2e8f0}.sbd-page__changes-table thead th{padding:7px 14px;text-align:left;font-size:10.5px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:#64748b;white-space:nowrap}.sbd-page__changes-table thead th .pi{margin-right:4px;font-size:10px}.sbd-page__changes-table thead th:nth-child(2){color:#dc2626}.sbd-page__changes-table thead th:nth-child(3){color:#16a34a}.sbd-page__changes-table tbody tr{border-bottom:1px solid #f1f5f9}.sbd-page__changes-table tbody tr:last-child{border-bottom:none}.sbd-page__changes-table tbody tr:hover{background:#f8fafc}.sbd-page__changes-table tbody td{padding:8px 14px;vertical-align:top;word-break:break-word}.sbd-page__changes-field{font-size:12px;font-weight:600;color:#475569;width:28%}.sbd-page__changes-old{color:#dc2626;font-weight:500;text-decoration:line-through;opacity:.8;width:36%}.sbd-page__changes-new{color:#16a34a;font-weight:700;width:36%}.sbd-page__content--loading{opacity:.6;pointer-events:none}.sbd-page__skeleton-card{background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:20px;display:flex;flex-direction:column;gap:12px}.sbd-page__skeleton-row{height:13px;background:#f1f5f9;border-radius:6px;animation:sbd-shimmer 1.4s ease-in-out infinite}.sbd-page__skeleton-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:10px;margin-top:8px}.sbd-page__skeleton-chip{height:56px;background:#f1f5f9;border-radius:8px;animation:sbd-shimmer 1.4s ease-in-out infinite}@keyframes sbd-shimmer{0%,to{opacity:1}50%{opacity:.45}}@keyframes sbd-fadein{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}\n"] }]
8211
+ ], template: "<div class=\"sbd-page\">\n\n <!-- Search bar full-width at top -->\n <div class=\"sbd-page__search\">\n <app-sbd-booking-input\n [(product)]=\"product\"\n [(bookingId)]=\"bookingId\"\n [loading]=\"bookingStatus() === 'loading'\"\n (lookup)=\"lookupBooking()\"\n />\n </div>\n\n <!-- Hotel: Use dedicated component -->\n @if (product() === 'hotel') {\n <app-sbd-hotel-content\n [bookingData]=\"bookingData()\"\n [bookingStatus]=\"bookingStatus()\"\n [bookingId]=\"bookingId()\"\n [dataVerified]=\"dataVerified()\"\n [bookingNotes]=\"bookingNotes()\"\n [bookingHistory]=\"bookingHistory()\"\n [historyGroups]=\"historyGroups()\"\n [activeContextTab]=\"activeContextTab()\"\n (cancelBooking)=\"cancelDialogOpen.set(true)\"\n (addNoteRequested)=\"addNoteDialogOpen.set(true)\"\n (contextTabChanged)=\"activeContextTab.set($event)\"\n />\n }\n\n <!-- Flight: Keep original 2-column layout -->\n @if (product() === 'flight') {\n <div class=\"sbd-page__content\">\n\n <!-- Left: booking context -->\n <aside class=\"sbd-page__aside\">\n\n <!-- Data search at the top of the sidebar -->\n @if (bookingStatus() === 'success') {\n <app-sbd-data-search [data]=\"bookingData()\" />\n <app-sbd-flight-itinerary-card [product]=\"product()\" [data]=\"bookingData()\" [verified]=\"dataVerified()\" />\n }\n\n <!-- Loading skeleton for aside -->\n @if (bookingStatus() === 'loading') {\n <div class=\"sbd-page__skeleton-card\">\n <div class=\"sbd-page__skeleton-row\" style=\"width:60%\"></div>\n <div class=\"sbd-page__skeleton-row\" style=\"width:40%\"></div>\n <div class=\"sbd-page__skeleton-row\" style=\"width:80%\"></div>\n <div class=\"sbd-page__skeleton-row\" style=\"width:50%\"></div>\n </div>\n }\n\n </aside>\n\n <!-- Right: agent workspace -->\n <main class=\"sbd-page__main\">\n\n <!-- Error state -->\n @if (bookingStatus() === 'error') {\n <div class=\"sbd-page__error\">\n <i class=\"pi pi-exclamation-circle\"></i>\n <span>Booking not found. Double-check the ID and product type, then try again.</span>\n </div>\n }\n\n <!-- Content once loaded -->\n @if (bookingStatus() === 'success') {\n\n <!-- Quick actions -->\n <div class=\"sbd-page__quick-actions\">\n <span class=\"sbd-page__quick-label\">Quick actions</span>\n <app-sbd-workflow-actions\n [bookingId]=\"bookingId()\"\n [showReissue]=\"reissueEnabled && product() === 'flight'\"\n (cancelBooking)=\"cancelDialogOpen.set(true)\"\n (reissue)=\"reissueDialogOpen.set(true)\"\n />\n </div>\n\n <!-- Action grid -->\n <app-sbd-action-grid\n [actions]=\"availableActions()\"\n [activeKey]=\"selectedActionKey()\"\n (actionSelected)=\"openAction($event)\"\n />\n\n <!-- Context: Notes + Changes tabbed -->\n <div class=\"sbd-page__context\">\n\n <!-- Tab bar -->\n <div class=\"sbd-page__context-tabs\">\n <button class=\"sbd-page__context-tab\"\n [class.sbd-page__context-tab--active]=\"activeContextTab() === 'changes'\"\n (click)=\"activeContextTab.set('changes')\">\n <i class=\"pi pi-history\"></i>\n Changes\n @if (bookingHistory().length > 0) {\n <span class=\"sbd-page__context-pill sbd-page__context-pill--changes\">{{ bookingHistory().length }}</span>\n }\n </button>\n <button class=\"sbd-page__context-tab\"\n [class.sbd-page__context-tab--active]=\"activeContextTab() === 'notes'\"\n (click)=\"activeContextTab.set('notes')\">\n <i class=\"pi pi-comments\"></i>\n Notes\n <span class=\"sbd-page__context-pill sbd-page__context-pill--notes\">{{ bookingNotes().length }}</span>\n </button>\n @if (activeContextTab() === 'notes') {\n <button pButton label=\"Add Note\" icon=\"pi pi-plus\" class=\"p-button-sm p-button-outlined sbd-page__context-add\"\n (click)=\"addNoteDialogOpen.set(true)\">\n </button>\n }\n </div>\n\n <!-- Notes tab -->\n @if (activeContextTab() === 'notes') {\n <div class=\"sbd-page__notes-list\">\n @if (bookingNotes().length === 0) {\n <div class=\"sbd-page__context-empty\">\n <i class=\"pi pi-inbox\"></i> No notes yet\n </div>\n }\n @for (note of bookingNotes(); track $index) {\n <div class=\"sbd-page__note\">\n <div class=\"sbd-page__note-top\">\n <span class=\"sbd-page__note-badge sbd-page__note-badge--{{ note.action | lowercase }}\">\n {{ note.action }}\n </span>\n <span class=\"sbd-page__note-author\">\n {{ note.writtenBy?.firstName }} {{ note.writtenBy?.lastName }}\n </span>\n <span class=\"sbd-page__note-date\">{{ fmtNoteDate(note.date) }}</span>\n </div>\n <p class=\"sbd-page__note-msg\">{{ note.message }}</p>\n </div>\n }\n </div>\n }\n\n <!-- Changes tab -->\n @if (activeContextTab() === 'changes') {\n <div class=\"sbd-page__changes-groups\">\n @if (historyGroups().length === 0) {\n <div class=\"sbd-page__context-empty\">\n <i class=\"pi pi-check\"></i> No changes recorded\n </div>\n }\n @for (group of historyGroups(); track group.changedAt) {\n <div class=\"sbd-page__changes-group\">\n <div class=\"sbd-page__changes-meta\">\n <i class=\"pi pi-user\"></i>\n <strong>{{ group.changedBy }}</strong>\n <span class=\"sbd-page__changes-sep\">\u00B7</span>\n <span>{{ fmtDate(group.changedAt) }}</span>\n </div>\n <table class=\"sbd-page__changes-table\">\n <thead>\n <tr>\n <th>Field</th>\n <th><i class=\"pi pi-times-circle\"></i> Before</th>\n <th><i class=\"pi pi-check-circle\"></i> After</th>\n </tr>\n </thead>\n <tbody>\n @for (entry of group.entries; track entry.field) {\n <tr>\n <td class=\"sbd-page__changes-field\">{{ formatField(entry.field) }}</td>\n <td class=\"sbd-page__changes-old\">{{ entry.from }}</td>\n <td class=\"sbd-page__changes-new\">{{ entry.to }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </div>\n }\n\n </div>\n\n }\n\n <!-- Loading skeleton for main -->\n @if (bookingStatus() === 'loading') {\n <div class=\"sbd-page__skeleton-card\">\n <div class=\"sbd-page__skeleton-row\" style=\"width:40%\"></div>\n <div class=\"sbd-page__skeleton-row\" style=\"width:25%\"></div>\n </div>\n }\n\n </main>\n\n </div>\n }\n\n</div>\n\n<app-sbd-cancel-dialog\n [visible]=\"cancelDialogOpen() && product() === 'flight'\"\n [bookingId]=\"bookingId()\"\n [product]=\"product()\"\n [pnrInfo]=\"cancelPnrInfo()\"\n [bookingInfo]=\"bookingData()\"\n (closed)=\"cancelDialogOpen.set(false)\"\n (confirmed)=\"bookingStatus.set('idle'); bookingData.set(null)\"\n/>\n\n<almatar-hotel-auto-cancellation\n *ngIf=\"cancelDialogOpen() && product() === 'hotel'\"\n [selectedHotelBooking]=\"bookingData()\"\n (closed)=\"cancelDialogOpen.set(false)\"\n (cancellationSuccess)=\"bookingStatus.set('idle'); bookingData.set(null)\"\n/>\n\n<app-sbd-action-dialog\n [visible]=\"actionDialogOpen()\"\n [bookingId]=\"bookingId()\"\n [product]=\"product()\"\n [actions]=\"availableActions()\"\n [initialKey]=\"selectedActionKey()\"\n [preloadedData]=\"bookingData()\"\n (closed)=\"actionDialogOpen.set(false)\"\n/>\n\n<app-sbd-add-note-dialog\n [visible]=\"addNoteDialogOpen()\"\n [bookingId]=\"bookingId()\"\n [product]=\"product()\"\n (closed)=\"addNoteDialogOpen.set(false)\"\n/>\n\n<app-sbd-reissue-dialog\n [visible]=\"reissueDialogOpen()\"\n [bookingId]=\"bookingId()\"\n [bookingData]=\"bookingData()\"\n (closed)=\"reissueDialogOpen.set(false)\"\n/>\n", styles: [".sbd-page{display:flex;flex-direction:column;gap:20px;padding:24px;max-width:1400px;min-height:100%}.sbd-page__search{width:100%}.sbd-page__error{display:flex;align-items:center;gap:10px;padding:16px 20px;background:#fef2f2;border:1px solid rgba(239,68,68,.2);border-radius:10px;color:#ef4444;font-size:13.5px;font-weight:500}.sbd-page__error i{font-size:18px}.sbd-page__content{display:grid;grid-template-columns:320px 1fr;gap:20px;align-items:start}@media (max-width: 960px){.sbd-page__content{grid-template-columns:1fr}}.sbd-page__aside{display:flex;flex-direction:column;gap:16px;position:sticky;top:20px}.sbd-page__main{display:flex;flex-direction:column;gap:16px}.sbd-page__quick-actions{display:flex;align-items:center;gap:12px;padding:12px 16px;background:#fff;border:1px solid #e2e8f0;border-radius:10px;flex-wrap:wrap}.sbd-page__quick-label{font-size:11.5px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:#94a3b8;flex-shrink:0}.sbd-page__context{background:#fff;border:1px solid #e2e8f0;border-radius:12px;overflow:hidden}.sbd-page__context-tabs{display:flex;align-items:center;border-bottom:1px solid #e2e8f0;background:#f8fafc}.sbd-page__context-add{margin-left:auto;margin-right:10px}.sbd-page__context-tab{display:flex;align-items:center;gap:7px;padding:11px 20px;font-size:13px;font-weight:600;color:#64748b;background:transparent;border:none;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px;transition:color .15s,border-color .15s}.sbd-page__context-tab .pi{font-size:13px}.sbd-page__context-tab:hover{color:#334155}.sbd-page__context-tab--active{color:#1e2e4a;border-bottom-color:var(--sbd-primary, #3b82f6);background:#fff}.sbd-page__context-pill{font-size:10.5px;font-weight:700;padding:1px 7px;border-radius:10px}.sbd-page__context-pill--notes{background:#e0e7ff;color:#4338ca}.sbd-page__context-pill--changes{background:#fef3c7;color:#b45309}.sbd-page__context-empty{display:flex;align-items:center;gap:8px;padding:24px 20px;font-size:13px;color:#94a3b8}.sbd-page__context-empty .pi{font-size:16px}.sbd-page__notes-list{display:flex;flex-direction:column}.sbd-page__note{padding:12px 16px;border-bottom:1px solid #f1f5f9;display:flex;flex-direction:column;gap:6px}.sbd-page__note:last-child{border-bottom:none}.sbd-page__note-top{display:flex;align-items:center;gap:8px}.sbd-page__note-badge{font-size:10px;font-weight:800;letter-spacing:.05em;padding:2px 8px;border-radius:10px;text-transform:uppercase;flex-shrink:0}.sbd-page__note-badge--general{background:#dbeafe;color:#1d4ed8}.sbd-page__note-badge--remark{background:#fef3c7;color:#b45309}.sbd-page__note-badge--feedback{background:#f3e8ff;color:#7e22ce}.sbd-page__note-author{font-size:12px;font-weight:700;color:#1e2e4a}.sbd-page__note-date{margin-left:auto;font-size:11px;color:#94a3b8;white-space:nowrap}.sbd-page__note-msg{margin:0;font-size:13px;color:#334155;line-height:1.55}.sbd-page__changes-groups{display:flex;flex-direction:column}.sbd-page__changes-group{border-bottom:1px solid #f1f5f9}.sbd-page__changes-group:last-child{border-bottom:none}.sbd-page__changes-meta{display:flex;align-items:center;gap:6px;padding:8px 16px;background:#fafafa;border-bottom:1px solid #f1f5f9;font-size:11.5px;color:#64748b}.sbd-page__changes-meta strong{font-weight:700;color:#334155}.sbd-page__changes-meta .pi{font-size:11px;color:#94a3b8}.sbd-page__changes-sep{color:#cbd5e1}.sbd-page__changes-table{width:100%;border-collapse:collapse;font-size:12.5px}.sbd-page__changes-table thead tr{background:#f8fafc;border-bottom:1px solid #e2e8f0}.sbd-page__changes-table thead th{padding:7px 14px;text-align:left;font-size:10.5px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:#64748b;white-space:nowrap}.sbd-page__changes-table thead th .pi{margin-right:4px;font-size:10px}.sbd-page__changes-table thead th:nth-child(2){color:#dc2626}.sbd-page__changes-table thead th:nth-child(3){color:#16a34a}.sbd-page__changes-table tbody tr{border-bottom:1px solid #f1f5f9}.sbd-page__changes-table tbody tr:last-child{border-bottom:none}.sbd-page__changes-table tbody tr:hover{background:#f8fafc}.sbd-page__changes-table tbody td{padding:8px 14px;vertical-align:top;word-break:break-word}.sbd-page__changes-field{font-size:12px;font-weight:600;color:#475569;width:28%}.sbd-page__changes-old{color:#dc2626;font-weight:500;text-decoration:line-through;opacity:.8;width:36%}.sbd-page__changes-new{color:#16a34a;font-weight:700;width:36%}.sbd-page__content--loading{opacity:.6;pointer-events:none}.sbd-page__skeleton-card{background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:20px;display:flex;flex-direction:column;gap:12px}.sbd-page__skeleton-row{height:13px;background:#f1f5f9;border-radius:6px;animation:sbd-shimmer 1.4s ease-in-out infinite}.sbd-page__skeleton-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:10px;margin-top:8px}.sbd-page__skeleton-chip{height:56px;background:#f1f5f9;border-radius:8px;animation:sbd-shimmer 1.4s ease-in-out infinite}@keyframes sbd-shimmer{0%,to{opacity:1}50%{opacity:.45}}@keyframes sbd-fadein{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}\n"] }]
6318
8212
  }], propDecorators: { onKeydown: [{
6319
8213
  type: HostListener,
6320
8214
  args: ['document:keydown', ['$event']]
@@ -6353,5 +8247,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
6353
8247
  * Generated bundle index. Do not edit.
6354
8248
  */
6355
8249
 
6356
- export { SBD_CONFIG, SBD_DEFAULT_CONFIG, SmartBookingDashboardComponent, SmartBookingDashboardModule, provideSmartBookingDashboard, smartBookingDashboardRoutes };
8250
+ export { SBD_CONFIG, SBD_DEFAULT_CONFIG, SBD_TOAST_HANDLER, SmartBookingDashboardComponent, SmartBookingDashboardModule, appendAuthParams, buildReissueBookingContext, buildReissueSearchParams, buildReissueSearchUrl, provideSmartBookingDashboard, smartBookingDashboardRoutes };
6357
8251
  //# sourceMappingURL=almatar-smart-booking-dashboard.mjs.map