@almatar/smart-booking-dashboard 0.0.1

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 (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +132 -0
  3. package/esm2022/almatar-smart-booking-dashboard.mjs +5 -0
  4. package/esm2022/lib/components/action-card/action-card.component.mjs +19 -0
  5. package/esm2022/lib/components/action-dialog/action-dialog.component.mjs +177 -0
  6. package/esm2022/lib/components/action-grid/action-grid.component.mjs +18 -0
  7. package/esm2022/lib/components/add-note-dialog/add-note-dialog.component.mjs +62 -0
  8. package/esm2022/lib/components/booking-input/booking-input.component.mjs +43 -0
  9. package/esm2022/lib/components/cancel-dialog/cancel-dialog.component.mjs +1441 -0
  10. package/esm2022/lib/components/data-search/data-search.component.mjs +84 -0
  11. package/esm2022/lib/components/flight-itinerary-card/flight-itinerary-card.component.mjs +73 -0
  12. package/esm2022/lib/components/result-panel/result-panel.component.mjs +46 -0
  13. package/esm2022/lib/components/workflow-actions/workflow-actions.component.mjs +19 -0
  14. package/esm2022/lib/config/dashboard.config.mjs +16 -0
  15. package/esm2022/lib/config/flight-actions.config.mjs +906 -0
  16. package/esm2022/lib/config/hotel-actions.config.mjs +53 -0
  17. package/esm2022/lib/config/labels.ar.mjs +116 -0
  18. package/esm2022/lib/models/action.types.mjs +2 -0
  19. package/esm2022/lib/models/booking.types.mjs +2 -0
  20. package/esm2022/lib/pages/smart-booking-dashboard/smart-booking-dashboard.component.mjs +145 -0
  21. package/esm2022/lib/services/booking.service.mjs +65 -0
  22. package/esm2022/lib/services/toast.service.mjs +19 -0
  23. package/esm2022/lib/smart-booking-dashboard.module.mjs +17 -0
  24. package/esm2022/lib/smart-booking-dashboard.routes.mjs +7 -0
  25. package/esm2022/public-api.mjs +9 -0
  26. package/fesm2022/almatar-smart-booking-dashboard.mjs +3259 -0
  27. package/fesm2022/almatar-smart-booking-dashboard.mjs.map +1 -0
  28. package/index.d.ts +5 -0
  29. package/lib/components/action-card/action-card.component.d.ts +10 -0
  30. package/lib/components/action-dialog/action-dialog.component.d.ts +26 -0
  31. package/lib/components/action-grid/action-grid.component.d.ts +10 -0
  32. package/lib/components/add-note-dialog/add-note-dialog.component.d.ts +25 -0
  33. package/lib/components/booking-input/booking-input.component.d.ts +23 -0
  34. package/lib/components/cancel-dialog/cancel-dialog.component.d.ts +395 -0
  35. package/lib/components/data-search/data-search.component.d.ts +21 -0
  36. package/lib/components/flight-itinerary-card/flight-itinerary-card.component.d.ts +17 -0
  37. package/lib/components/result-panel/result-panel.component.d.ts +14 -0
  38. package/lib/components/workflow-actions/workflow-actions.component.d.ts +7 -0
  39. package/lib/config/dashboard.config.d.ts +15 -0
  40. package/lib/config/flight-actions.config.d.ts +2 -0
  41. package/lib/config/hotel-actions.config.d.ts +2 -0
  42. package/lib/config/labels.ar.d.ts +2 -0
  43. package/lib/models/action.types.d.ts +34 -0
  44. package/lib/models/booking.types.d.ts +6 -0
  45. package/lib/pages/smart-booking-dashboard/smart-booking-dashboard.component.d.ts +43 -0
  46. package/lib/services/booking.service.d.ts +22 -0
  47. package/lib/services/toast.service.d.ts +10 -0
  48. package/lib/smart-booking-dashboard.module.d.ts +7 -0
  49. package/lib/smart-booking-dashboard.routes.d.ts +2 -0
  50. package/package.json +50 -0
  51. package/public-api.d.ts +5 -0
@@ -0,0 +1,3259 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, makeEnvironmentProviders, inject, Injectable, input, model, output, Component, computed, signal, effect, HostListener, NgModule } from '@angular/core';
3
+ import { NgTemplateOutlet, NgClass, LowerCasePipe, UpperCasePipe } from '@angular/common';
4
+ import * as i3$1 from 'primeng/button';
5
+ import { ButtonModule } from 'primeng/button';
6
+ import { HttpClient } from '@angular/common/http';
7
+ import { map, catchError } from 'rxjs/operators';
8
+ import { ToastrService } from 'ngx-toastr';
9
+ import * as i1 from '@angular/forms';
10
+ import { FormsModule } from '@angular/forms';
11
+ import * as i2 from 'primeng/selectbutton';
12
+ import { SelectButtonModule } from 'primeng/selectbutton';
13
+ import * as i3 from 'primeng/inputtext';
14
+ import { InputTextModule } from 'primeng/inputtext';
15
+ import * as i5 from 'primeng/tooltip';
16
+ import { TooltipModule } from 'primeng/tooltip';
17
+ import * as i1$1 from 'primeng/dialog';
18
+ import { DialogModule } from 'primeng/dialog';
19
+ import * as i1$2 from 'primeng/skeleton';
20
+ import { SkeletonModule } from 'primeng/skeleton';
21
+ import { TagModule } from 'primeng/tag';
22
+ import * as i5$1 from '@ng-select/ng-select';
23
+ import { NgSelectModule } from '@ng-select/ng-select';
24
+ import { of } from 'rxjs';
25
+ import { trigger, transition, style, animate } from '@angular/animations';
26
+ import * as i2$1 from 'primeng/api';
27
+ import * as i1$3 from '@angular/router';
28
+ import { RouterModule } from '@angular/router';
29
+
30
+ const SBD_DEFAULT_CONFIG = {
31
+ apiBase: '/api',
32
+ platform: 'BO',
33
+ };
34
+ const SBD_CONFIG = new InjectionToken('SBD_CONFIG', {
35
+ providedIn: 'root',
36
+ factory: () => SBD_DEFAULT_CONFIG,
37
+ });
38
+ /** Optional: call in the host app's providers to override the defaults. */
39
+ function provideSmartBookingDashboard(config = {}) {
40
+ return makeEnvironmentProviders([
41
+ { provide: SBD_CONFIG, useValue: { ...SBD_DEFAULT_CONFIG, ...config } },
42
+ ]);
43
+ }
44
+
45
+ class SbdBookingService {
46
+ constructor() {
47
+ this.http = inject(HttpClient);
48
+ this.config = inject(SBD_CONFIG);
49
+ this.base = this.config.apiBase;
50
+ }
51
+ getBooking(id, product, fields = 'all', language = 'en') {
52
+ const start = performance.now();
53
+ const fieldsParam = Array.isArray(fields) ? fields.join(',') : fields;
54
+ const productParam = product === 'flight' ? 'flights' : 'hotels';
55
+ const headers = { platform: this.config.platform, 'Accept-Language': language };
56
+ return this.http
57
+ .get(`${this.base}/v1/fcr/booking/information/${id}`, {
58
+ params: { product: productParam, fields: fieldsParam, addAirlineStamp: 'true' },
59
+ headers,
60
+ })
61
+ .pipe(map(data => ({ data, elapsedMs: performance.now() - start })));
62
+ }
63
+ resendTicket(id) {
64
+ return this.http.post(`${this.base}/v1/fcr/booking/${id}/resend`, {});
65
+ }
66
+ getRefundDetails(id, product = 'flight', language = 'en') {
67
+ const productParam = product === 'flight' ? 'flights' : 'hotels';
68
+ const headers = { platform: this.config.platform };
69
+ if (language === 'ar')
70
+ headers['Accept-Language'] = 'ar';
71
+ return this.http.post(`${this.base}/v1/fcr/refund/details`, {
72
+ bookingId: id,
73
+ product: productParam,
74
+ }, { headers });
75
+ }
76
+ addNote(id, product = 'flight', message, action = 'GENERAL') {
77
+ const productParam = product === 'flight' ? 'flight' : 'hotel';
78
+ return this.http.put(`${this.base}/v1/back-office/booking/${productParam}/addNote/${id}`, { notes: [{ message, action }] });
79
+ }
80
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
81
+ confirmCancel(id, product = 'flight', legInfo, selectedSource, preferredRefundMethod, splitAmounts) {
82
+ const productParam = product === 'flight' ? 'flights' : 'hotels';
83
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
+ const body = { bookingId: String(id), product: productParam };
85
+ if (legInfo && legInfo.length > 0)
86
+ body.legInfo = legInfo;
87
+ if (selectedSource)
88
+ body.selectedSource = selectedSource;
89
+ if (preferredRefundMethod)
90
+ body.preferredRefundMethod = preferredRefundMethod;
91
+ if (preferredRefundMethod === 'split' && splitAmounts) {
92
+ body.walletRefundAmount = splitAmounts.walletRefundAmount;
93
+ body.totalCardRefundAmount = splitAmounts.totalCardRefundAmount;
94
+ }
95
+ return this.http.post(`${this.base}/v1/fcr/refund`, body, { headers: { platform: this.config.platform } });
96
+ }
97
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdBookingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
98
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdBookingService, providedIn: 'root' }); }
99
+ }
100
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdBookingService, decorators: [{
101
+ type: Injectable,
102
+ args: [{ providedIn: 'root' }]
103
+ }] });
104
+
105
+ class SbdToastService {
106
+ constructor() {
107
+ this.toastr = inject(ToastrService);
108
+ }
109
+ success(msg) { this.toastr.success(msg); }
110
+ error(msg) { this.toastr.error(msg); }
111
+ info(msg) { this.toastr.info(msg); }
112
+ warning(msg) { this.toastr.warning(msg); }
113
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdToastService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
114
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdToastService, providedIn: 'root' }); }
115
+ }
116
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdToastService, decorators: [{
117
+ type: Injectable,
118
+ args: [{ providedIn: 'root' }]
119
+ }] });
120
+
121
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
122
+ const na$1 = (v) => v != null && v !== '' ? String(v) : 'Not available';
123
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
124
+ const bi = (data) => data?.data?.booking_info;
125
+ const fmtMins = (mins) => mins != null ? `${Math.floor(mins / 60)}h ${mins % 60}m` : 'Not available';
126
+ const fmtDate = (dt) => {
127
+ if (!dt)
128
+ return 'Not available';
129
+ try {
130
+ return new Date(dt).toLocaleString('en-GB', {
131
+ day: '2-digit', month: 'short', year: 'numeric',
132
+ hour: '2-digit', minute: '2-digit',
133
+ });
134
+ }
135
+ catch {
136
+ return dt;
137
+ }
138
+ };
139
+ const dirIcon = (dir) => (dir === 'Return' || dir === 'return') ? '←' : '→';
140
+ /**
141
+ * Format a baggage allowance object (CabinBaggage / CheckedBaggage / UnderSeatBaggage).
142
+ * Negative values (-1) indicate the allowance is not applicable.
143
+ */
144
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
145
+ const fmtBag = (bag) => {
146
+ if (!bag)
147
+ return 'Not included';
148
+ const { Pieces, Weight, Unit, Dimension, Remark } = bag;
149
+ // Negative Pieces = not applicable (e.g. Pieces: -1, Weight: -1)
150
+ if (Pieces != null && Pieces < 0 && (Weight == null || Weight < 0))
151
+ return 'Not included';
152
+ // Zero across the board = not included
153
+ if (Pieces === 0 && (Weight === 0 || Weight == null))
154
+ return 'Not included';
155
+ const parts = [];
156
+ if (Pieces != null && Pieces > 0)
157
+ parts.push(`${Pieces} pc${Pieces > 1 ? 's' : ''}`);
158
+ if (Weight != null && Weight > 0)
159
+ parts.push(`${Weight} ${Unit ?? 'KG'}`);
160
+ if (Dimension)
161
+ parts.push(Dimension);
162
+ if (Remark)
163
+ parts.push(Remark);
164
+ return parts.length > 0 ? parts.join(' · ') : 'Not available';
165
+ };
166
+ /**
167
+ * Resolve pnr_info to a flat array regardless of shape.
168
+ * New BE format: [{pnr, LegDirectionality}, ...]
169
+ * Legacy format: {pnr: '...'} or null
170
+ */
171
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
172
+ const resolvePnrList = (pnrRaw) => {
173
+ if (Array.isArray(pnrRaw))
174
+ return pnrRaw;
175
+ if (pnrRaw?.pnr)
176
+ return [{ pnr: pnrRaw.pnr, LegDirectionality: 'Departure' }];
177
+ return [];
178
+ };
179
+ const FLIGHT_ACTIONS = [
180
+ // ── Booking Overview ──────────────────────────────────────────────────────
181
+ {
182
+ key: 'booking_info',
183
+ type: 'flight',
184
+ label: 'Booking Overview',
185
+ subtitle: 'Route · Class · Fare',
186
+ icon: 'pi pi-file',
187
+ apiField: 'booking_info',
188
+ formatter: (data) => {
189
+ const d = bi(data);
190
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
191
+ const legs = d?.leg_info ?? [];
192
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
193
+ const airlines = d?.airline_info ?? [];
194
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
195
+ const passengers = d?.passenger_info?.passengers ?? [];
196
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
197
+ const baggage = d?.baggage;
198
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
199
+ const dur = d?.duration;
200
+ const ps = d?.payment_status;
201
+ const cp = d?.cancellation_policy;
202
+ const pnrList = resolvePnrList(d?.pnr_info);
203
+ const pnrDep = pnrList.find((p) => p.LegDirectionality === 'Departure')?.pnr ?? pnrList[0]?.pnr;
204
+ const pnrRet = pnrList.find((p) => p.LegDirectionality === 'Return')?.pnr;
205
+ const origin = legs[0]?.LegDepartureAirportCode;
206
+ const dest = legs[legs.length - 1]?.LegArrivalAirportCode;
207
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
208
+ const sections = [];
209
+ // ── Booking Reference ────────────────────────────────────────────────
210
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
211
+ const refRows = [];
212
+ if (pnrList.length > 1) {
213
+ pnrList.forEach((p) => refRows.push({ label: `PNR (${p.LegDirectionality})`, value: na$1(p.pnr) }));
214
+ }
215
+ else {
216
+ refRows.push({ label: 'PNR', value: na$1(pnrDep) });
217
+ }
218
+ refRows.push({ label: 'Booking ID', value: na$1(cp?.bookingId) }, { label: 'Supplier ref', value: na$1(cp?.supplierBookingId) }, { label: 'Supplier', value: na$1(cp?.supplierName ?? legs[0]?.supplierName) });
219
+ sections.push({ title: 'Booking Reference', rows: refRows });
220
+ // ── Per-leg sections ─────────────────────────────────────────────────
221
+ legs.forEach((l, i) => {
222
+ const dir = l.LegDirectionality ?? '';
223
+ const from = l.LegDepartureAirportCode;
224
+ const to = l.LegArrivalAirportCode;
225
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
226
+ const segs = airlines[i]?.Segments ?? [];
227
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
228
+ const durSegs = dur?.legs?.[i]?.Segments ?? [];
229
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
230
+ const stops = l.Stops ?? [];
231
+ const legDurMins = durSegs.reduce((sum, s) => sum + (s.SegmentElapsedTimeMinutes ?? 0), 0);
232
+ const flightNos = segs.map((s) => `${s.AirlineCode}${s.FlightNumber}`).filter(Boolean).join(', ');
233
+ sections.push({
234
+ title: `${dirIcon(dir)} ${dir || `Leg ${i + 1}`} · ${from} → ${to}`,
235
+ rows: [
236
+ { label: 'Departure', value: fmtDate(l.LegDepartureDateTime) },
237
+ { label: 'Arrival', value: fmtDate(l.LegArrivalDateTime) },
238
+ { label: 'Duration', value: legDurMins > 0 ? fmtMins(legDurMins) : na$1(null) },
239
+ { label: 'Airline(s)', value: na$1(l.LegAirlinesNames?.join(', ')) },
240
+ { label: 'Flight no.', value: na$1(flightNos) },
241
+ { label: 'Cabin', value: na$1(l.LegCabins?.[0]?.Name) },
242
+ { label: 'Fare brand', value: na$1(l.fareBrand?.name) },
243
+ {
244
+ label: 'Stops',
245
+ value: stops.length === 0
246
+ ? 'Non-stop'
247
+ : stops.map((s) => `${s.StopAirportCity ?? s.StopAirport} (${fmtMins(s.LayoverDuration)})`).join(', '),
248
+ },
249
+ ],
250
+ });
251
+ });
252
+ // ── Passengers ───────────────────────────────────────────────────────
253
+ const paxTypeLabel = { ADT: 'Adult', CNN: 'Child', INF: 'Infant' };
254
+ passengers.forEach((p, i) => {
255
+ const fullName = [p?.name?.prefix, p?.name?.givenName, p?.name?.surName].filter(Boolean).join(' ');
256
+ const phone = p?.phone ?? (p?.contactNumber?.phoneNumber
257
+ ? `${p.contactNumber.countryCode ?? ''}${p.contactNumber.phoneNumber}` : null);
258
+ sections.push({
259
+ title: `Passenger ${i + 1} · ${fullName || na$1(null)}`,
260
+ rows: [
261
+ { label: 'Type', value: na$1(paxTypeLabel[p?.type] ?? p?.type) },
262
+ { label: 'Gender', value: na$1(p?.gender) },
263
+ { label: 'Date of birth', value: na$1(p?.dateOfBirth) },
264
+ { label: 'Passport', value: na$1(p?.identity?.number) },
265
+ { label: 'Nationality', value: na$1(p?.identity?.nationality) },
266
+ { label: 'Passport exp.', value: na$1(p?.identity?.expirationDate) },
267
+ { label: 'Issue country', value: na$1(p?.identity?.issueCountry) },
268
+ { label: 'Doc type', value: na$1(p?.identity?.type) },
269
+ { label: 'Email', value: na$1(p?.email) },
270
+ { label: 'Phone', value: na$1(phone) },
271
+ ],
272
+ });
273
+ });
274
+ // ── Payment ──────────────────────────────────────────────────────────
275
+ if (ps) {
276
+ const statusName = ps?.status?.name ?? ps?.status?.key;
277
+ const paid = ps?.amountInPaidCurrency != null && ps?.paidCurrency
278
+ ? `${ps.amountInPaidCurrency} ${ps.paidCurrency}` : null;
279
+ sections.push({
280
+ title: 'Payment',
281
+ rows: [
282
+ { label: 'Status', value: na$1(statusName) },
283
+ { label: 'Amount', value: ps?.amount != null ? `${ps.amount} ${na$1(ps.currency)}` : 'Not available' },
284
+ { label: 'Paid amount', value: na$1(paid) },
285
+ { label: 'Payment method', value: na$1(ps?.method) },
286
+ { label: 'Date', value: fmtDate(ps?.date) },
287
+ ],
288
+ });
289
+ }
290
+ // ── Baggage per route (full detail) ──────────────────────────────────
291
+ const baggageInfo = baggage?.baggage_info ?? [];
292
+ baggageInfo.forEach((entry) => {
293
+ const route = Object.keys(entry)[0];
294
+ if (!route)
295
+ return;
296
+ const paxTypes = entry[route] ?? {};
297
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
298
+ const rows = [];
299
+ Object.entries(paxTypes).forEach(([paxType, pd]) => {
300
+ const label = paxType === 'ADT' ? 'Adult' : paxType === 'CNN' ? 'Child' : paxType === 'INF' ? 'Infant' : paxType;
301
+ const cabin = pd.CabinBaggage;
302
+ const checked = pd.CheckedBaggage;
303
+ const underSeat = pd.UnderSeatBaggage;
304
+ rows.push({ label: `${label} · Cabin`, value: fmtBag(cabin) }, { label: `${label} · Checked`, value: fmtBag(checked) });
305
+ if (underSeat)
306
+ rows.push({ label: `${label} · Under Seat`, value: fmtBag(underSeat) });
307
+ });
308
+ sections.push({ title: `Baggage · ${route}`, rows });
309
+ });
310
+ // ── Terminal Info summary ────────────────────────────────────────────
311
+ const terminalData = d?.terminal ?? [];
312
+ terminalData.forEach((tl, li) => {
313
+ const legMeta = legs[li];
314
+ const legLabel = legMeta
315
+ ? `Terminal · ${legMeta.LegDepartureAirportCode} → ${legMeta.LegArrivalAirportCode}`
316
+ : `Terminal · Leg ${li + 1}`;
317
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
318
+ const termRows = [];
319
+ (tl.Segments ?? []).forEach((s, si) => {
320
+ const airlineSeg = airlines[li]?.Segments?.[si];
321
+ const flightNo = airlineSeg ? `${airlineSeg.AirlineCode}${airlineSeg.FlightNumber}` : `Seg ${si + 1}`;
322
+ const depTerm = s.DepartureAirportTerminalID ? `T${s.DepartureAirportTerminalID}` : '—';
323
+ const arrTerm = s.ArrivalAirportTerminalID ? `T${s.ArrivalAirportTerminalID}` : '—';
324
+ termRows.push({
325
+ label: flightNo,
326
+ value: `${na$1(s.DepartureAirportCode)} ${depTerm} → ${na$1(s.ArrivalAirportCode)} ${arrTerm}`,
327
+ });
328
+ });
329
+ if (termRows.length > 0)
330
+ sections.push({ title: legLabel, rows: termRows });
331
+ });
332
+ // ── Cancellation Policy summary ──────────────────────────────────────
333
+ const cpLegs = cp?.legs ?? [];
334
+ cpLegs.forEach((leg) => {
335
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
336
+ const cpRows = [];
337
+ (leg.passengerPenalties ?? []).forEach((pp) => {
338
+ const typeLabel = pp.type === 'ADT' ? 'Adult' : pp.type === 'CNN' ? 'Child' : 'Infant';
339
+ const bDep = pp.penalties?.beforeDeparture;
340
+ const refund = bDep?.refund;
341
+ const exchange = bDep?.exchange;
342
+ const refundStr = refund?.isActionable ? `Refund: ${refund.fee} ${refund.currency}` : 'No refund';
343
+ const exchangeStr = exchange?.isActionable ? `Exchange: ${exchange.fee} ${exchange.currency}` : 'No exchange';
344
+ cpRows.push({ label: typeLabel, value: `${refundStr} · ${exchangeStr}` });
345
+ });
346
+ if (cpRows.length > 0) {
347
+ sections.push({
348
+ title: `Cancellation and Change Policy · ${leg.direction} (${leg.from} → ${leg.to})`,
349
+ rows: cpRows,
350
+ });
351
+ }
352
+ });
353
+ return {
354
+ title: origin ? `${origin} → ${dest}` : 'Flight Booking',
355
+ subtitle: pnrDep
356
+ ? `PNR: ${pnrDep}${pnrRet ? ' / ' + pnrRet : ''} · ${legs.length} leg${legs.length !== 1 ? 's' : ''} · ${passengers.length} pax`
357
+ : `${legs.length} leg${legs.length !== 1 ? 's' : ''} · ${passengers.length} pax`,
358
+ sections,
359
+ };
360
+ },
361
+ },
362
+ // ── Terminal Info ─────────────────────────────────────────────────────────
363
+ {
364
+ key: 'terminal',
365
+ type: 'flight',
366
+ label: 'Terminal Info',
367
+ subtitle: 'Gate · Check-in',
368
+ icon: 'pi pi-send',
369
+ apiField: 'booking_info',
370
+ formatter: (data) => {
371
+ const d = bi(data);
372
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
373
+ const terminalLegs = d?.terminal ?? [];
374
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
375
+ const legInfos = d?.leg_info ?? [];
376
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
377
+ const airlines = d?.airline_info ?? [];
378
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
379
+ const sections = [];
380
+ terminalLegs.forEach((tl, li) => {
381
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
382
+ const segs = tl.Segments ?? [];
383
+ const legMeta = legInfos[li];
384
+ const legLabel = legMeta
385
+ ? `${dirIcon(legMeta.LegDirectionality)} ${legMeta.LegDepartureAirportCode} → ${legMeta.LegArrivalAirportCode}`
386
+ : `Leg ${li + 1}`;
387
+ // Leg-level times as fallback when segment-level times are absent
388
+ const legDepTime = legMeta?.LegDepartureDateTime;
389
+ const legArrTime = legMeta?.LegArrivalDateTime;
390
+ segs.forEach((s, si) => {
391
+ const airlineSeg = airlines[li]?.Segments?.[si];
392
+ // Resolve flight number: terminal seg → airline_info seg
393
+ const flightNo = s.FlightNumber
394
+ ? `${s.AirlineCode ?? ''}${s.FlightNumber}`
395
+ : airlineSeg ? `${airlineSeg.AirlineCode}${airlineSeg.FlightNumber}` : null;
396
+ // Resolve times: terminal seg → airline_info seg → leg-level fallback
397
+ const depTime = s.DepartureDateTime
398
+ ?? airlineSeg?.DepartureDateTime
399
+ ?? (si === 0 ? legDepTime : null);
400
+ const arrTime = s.ArrivalDateTime
401
+ ?? airlineSeg?.ArrivalDateTime
402
+ ?? (si === segs.length - 1 ? legArrTime : null);
403
+ sections.push({
404
+ title: `${legLabel} · ${flightNo ?? `Seg ${si + 1}`}`,
405
+ rows: [
406
+ { label: 'Departure airport', value: s.DepartureAirportName ? `${s.DepartureAirportName} (${s.DepartureAirportCode})` : na$1(s.DepartureAirportCode) },
407
+ { label: 'Dep. terminal', value: na$1(s.DepartureAirportTerminalID) },
408
+ { label: 'Arrival airport', value: s.ArrivalAirportName ? `${s.ArrivalAirportName} (${s.ArrivalAirportCode})` : na$1(s.ArrivalAirportCode) },
409
+ { label: 'Arr. terminal', value: na$1(s.ArrivalAirportTerminalID) },
410
+ { label: 'Flight', value: na$1(flightNo) },
411
+ { label: 'Departure time', value: fmtDate(depTime) },
412
+ { label: 'Arrival time', value: fmtDate(arrTime) },
413
+ ],
414
+ });
415
+ });
416
+ });
417
+ const totalSegs = sections.length;
418
+ return {
419
+ title: 'Terminal Details',
420
+ subtitle: `${totalSegs} segment${totalSegs !== 1 ? 's' : ''}`,
421
+ sections: sections.length
422
+ ? sections
423
+ : [{ title: 'Info', rows: [{ label: 'Status', value: 'No terminal data available' }] }],
424
+ };
425
+ },
426
+ },
427
+ // ── Payment Status ────────────────────────────────────────────────────────
428
+ {
429
+ key: 'payment_status',
430
+ type: 'flight',
431
+ hidden: true,
432
+ label: 'Payment Status',
433
+ subtitle: 'Charges · Receipt',
434
+ icon: 'pi pi-credit-card',
435
+ apiField: 'booking_info',
436
+ formatter: (data) => {
437
+ const ps = bi(data)?.payment_status;
438
+ const statusName = ps?.status?.name ?? ps?.status?.key;
439
+ const paid = ps?.amountInPaidCurrency != null && ps?.paidCurrency
440
+ ? `${ps.amountInPaidCurrency} ${ps.paidCurrency}` : null;
441
+ const isSuccess = ps?.status?.key === 'SUCCESS';
442
+ return {
443
+ title: 'Payment Details',
444
+ subtitle: na$1(statusName),
445
+ sections: [
446
+ {
447
+ title: 'Transaction',
448
+ rows: [
449
+ { label: 'Status', value: na$1(statusName), valueClass: isSuccess ? 'positive' : 'neutral' },
450
+ { label: 'Amount', value: ps?.amount != null ? `${ps.amount} ${na$1(ps.currency)}` : 'Not available' },
451
+ { label: 'Paid amount', value: na$1(paid) },
452
+ { label: 'Payment method', value: na$1(ps?.method) },
453
+ { label: 'Transaction ref', value: na$1(ps?.transactionRef ?? ps?.reference) },
454
+ { label: 'Date', value: fmtDate(ps?.date) },
455
+ { label: 'Currency', value: na$1(ps?.currency) },
456
+ ],
457
+ },
458
+ ],
459
+ };
460
+ },
461
+ },
462
+ // ── Passenger Info ────────────────────────────────────────────────────────
463
+ {
464
+ key: 'passenger_info',
465
+ type: 'flight',
466
+ hidden: true,
467
+ label: 'Passenger Info',
468
+ subtitle: 'Names · Docs',
469
+ icon: 'pi pi-users',
470
+ apiField: 'booking_info',
471
+ formatter: (data) => {
472
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
473
+ const passengers = bi(data)?.passenger_info?.passengers ?? [];
474
+ const typeLabel = { ADT: 'Adult', CNN: 'Child', INF: 'Infant' };
475
+ return {
476
+ title: 'Passenger Details',
477
+ subtitle: `${passengers.length} traveller${passengers.length !== 1 ? 's' : ''}`,
478
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
479
+ sections: passengers.map((p, i) => {
480
+ const fullName = [p?.name?.prefix, p?.name?.givenName, p?.name?.surName].filter(Boolean).join(' ');
481
+ const phone = p?.phone ?? (p?.contactNumber?.phoneNumber
482
+ ? `${p.contactNumber.countryCode ?? ''}${p.contactNumber.phoneNumber}` : null);
483
+ return {
484
+ title: `Passenger ${i + 1} · ${fullName || na$1(null)}`,
485
+ rows: [
486
+ { label: 'Type', value: na$1(typeLabel[p?.type] ?? p?.type) },
487
+ { label: 'Gender', value: na$1(p?.gender) },
488
+ { label: 'Date of birth', value: na$1(p?.dateOfBirth) },
489
+ { label: 'Passport', value: na$1(p?.identity?.number) },
490
+ { label: 'Nationality', value: na$1(p?.identity?.nationality) },
491
+ { label: 'Passport exp.', value: na$1(p?.identity?.expirationDate) },
492
+ { label: 'Issue country', value: na$1(p?.identity?.issueCountry) },
493
+ { label: 'Doc type', value: na$1(p?.identity?.type) },
494
+ { label: 'Email', value: na$1(p?.email) },
495
+ { label: 'Phone', value: na$1(phone) },
496
+ ],
497
+ };
498
+ }),
499
+ };
500
+ },
501
+ },
502
+ // ── Passenger Count ───────────────────────────────────────────────────────
503
+ {
504
+ key: 'passenger_count',
505
+ type: 'flight',
506
+ hidden: true,
507
+ label: 'Passenger Count',
508
+ subtitle: 'Adults · Children · Infants',
509
+ icon: 'pi pi-users',
510
+ apiField: 'booking_info',
511
+ formatter: (data) => {
512
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
513
+ const passengers = bi(data)?.passenger_info?.passengers ?? [];
514
+ const adults = passengers.filter(p => p?.type === 'ADT');
515
+ const children = passengers.filter(p => p?.type === 'CNN');
516
+ const infants = passengers.filter(p => p?.type === 'INF');
517
+ return {
518
+ title: 'Traveller Breakdown',
519
+ subtitle: `${passengers.length} total`,
520
+ sections: [
521
+ {
522
+ title: 'Summary',
523
+ rows: [
524
+ { label: 'Total', value: String(passengers.length) },
525
+ { label: 'Adults (ADT)', value: String(adults.length) },
526
+ { label: 'Children (CNN)', value: String(children.length) },
527
+ { label: 'Infants (INF)', value: String(infants.length) },
528
+ ],
529
+ },
530
+ ...adults.map((p, i) => ({
531
+ title: `Adult ${i + 1} · ${[p?.name?.prefix, p?.name?.givenName, p?.name?.surName].filter(Boolean).join(' ') || 'N/A'}`,
532
+ rows: [
533
+ { label: 'Passport', value: na$1(p?.identity?.number) },
534
+ { label: 'Email', value: na$1(p?.email) },
535
+ ],
536
+ })),
537
+ ...children.map((p, i) => ({
538
+ title: `Child ${i + 1} · ${[p?.name?.prefix, p?.name?.givenName, p?.name?.surName].filter(Boolean).join(' ') || 'N/A'}`,
539
+ rows: [
540
+ { label: 'Passport', value: na$1(p?.identity?.number) },
541
+ { label: 'DOB', value: na$1(p?.dateOfBirth) },
542
+ ],
543
+ })),
544
+ ...infants.map((p, i) => ({
545
+ title: `Infant ${i + 1} · ${[p?.name?.prefix, p?.name?.givenName, p?.name?.surName].filter(Boolean).join(' ') || 'N/A'}`,
546
+ rows: [
547
+ { label: 'DOB', value: na$1(p?.dateOfBirth) },
548
+ ],
549
+ })),
550
+ ],
551
+ };
552
+ },
553
+ },
554
+ // ── Airline Info ──────────────────────────────────────────────────────────
555
+ {
556
+ key: 'airline_info',
557
+ type: 'flight',
558
+ hidden: true,
559
+ label: 'Airline Info',
560
+ subtitle: 'Carrier · Flight no.',
561
+ icon: 'pi pi-send',
562
+ apiField: 'booking_info',
563
+ formatter: (data) => {
564
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
565
+ const airlines = bi(data)?.airline_info ?? [];
566
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
567
+ const legInfos = bi(data)?.leg_info ?? [];
568
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
569
+ const durLegs = bi(data)?.duration?.legs ?? [];
570
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
571
+ const sections = [];
572
+ airlines.forEach((a, li) => {
573
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
574
+ const segs = a.Segments ?? [];
575
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
576
+ const durSegs = durLegs[li]?.Segments ?? [];
577
+ const legMeta = legInfos[li];
578
+ const legLabel = legMeta
579
+ ? `${dirIcon(legMeta.LegDirectionality)} ${legMeta.LegDepartureAirportCode} → ${legMeta.LegArrivalAirportCode}`
580
+ : `Leg ${li + 1}`;
581
+ // Leg-level times as fallback for first/last segment
582
+ const legDepTime = legMeta?.LegDepartureDateTime;
583
+ const legArrTime = legMeta?.LegArrivalDateTime;
584
+ segs.forEach((s, si) => {
585
+ // Use segment-level times if available, fall back to leg times
586
+ const depTime = s.DepartureDateTime ?? (si === 0 ? legDepTime : null);
587
+ const arrTime = s.ArrivalDateTime ?? (si === segs.length - 1 ? legArrTime : null);
588
+ sections.push({
589
+ title: `${legLabel} · ${s.AirlineCode}${s.FlightNumber} (Seg ${si + 1})`,
590
+ rows: [
591
+ { label: 'Airline', value: na$1(s.AirlineName) },
592
+ { label: 'IATA code', value: na$1(s.AirlineCode) },
593
+ { label: 'Flight no.', value: s.FlightNumber ? `${s.AirlineCode}${s.FlightNumber}` : 'Not available' },
594
+ { label: 'Route', value: `${na$1(s.DepartureAirportCode)} → ${na$1(s.ArrivalAirportCode)}` },
595
+ { label: 'Departure', value: fmtDate(depTime) },
596
+ { label: 'Arrival', value: fmtDate(arrTime) },
597
+ { label: 'Duration', value: durSegs[si]?.SegmentElapsedTimeMinutes ? fmtMins(durSegs[si].SegmentElapsedTimeMinutes) : 'Not available' },
598
+ { label: 'Aircraft', value: na$1(s.AircraftType ?? s.Equipment) },
599
+ { label: 'Operating code', value: na$1(s.OperatingAirlineCode) },
600
+ ],
601
+ });
602
+ });
603
+ });
604
+ const totalSegs = sections.length;
605
+ return {
606
+ title: airlines[0]?.Segments?.[0]?.AirlineName ?? 'Airline Info',
607
+ subtitle: `${totalSegs} segment${totalSegs !== 1 ? 's' : ''}`,
608
+ sections,
609
+ };
610
+ },
611
+ },
612
+ // ── Leg Info ──────────────────────────────────────────────────────────────
613
+ {
614
+ key: 'leg_info',
615
+ type: 'flight',
616
+ hidden: true,
617
+ label: 'Leg Info',
618
+ subtitle: 'Route · Stops · Cabin',
619
+ icon: 'pi pi-map-marker',
620
+ apiField: 'booking_info',
621
+ formatter: (data) => {
622
+ const d = bi(data);
623
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
624
+ const legs = d?.leg_info ?? [];
625
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
626
+ const airlines = d?.airline_info ?? [];
627
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
628
+ const durLegs = d?.duration?.legs ?? [];
629
+ return {
630
+ title: 'Flight Legs',
631
+ subtitle: `${legs.length} leg${legs.length !== 1 ? 's' : ''}`,
632
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
633
+ sections: legs.map((l, i) => {
634
+ const dir = l.LegDirectionality ?? '';
635
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
636
+ const stops = l.Stops ?? [];
637
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
638
+ const segs = airlines[i]?.Segments ?? [];
639
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
640
+ const durSegs = durLegs[i]?.Segments ?? [];
641
+ const legDurMins = durSegs.reduce((sum, s) => sum + (s.SegmentElapsedTimeMinutes ?? 0), 0);
642
+ const flightNos = segs.map((s) => `${s.AirlineCode}${s.FlightNumber}`).filter(Boolean).join(', ');
643
+ return {
644
+ title: `${dirIcon(dir)} ${dir || `Leg ${i + 1}`} · ${l.LegDepartureAirportCode} → ${l.LegArrivalAirportCode}`,
645
+ rows: [
646
+ { label: 'Direction', value: na$1(dir) },
647
+ { label: 'Departure', value: fmtDate(l.LegDepartureDateTime) },
648
+ { label: 'Arrival', value: fmtDate(l.LegArrivalDateTime) },
649
+ { label: 'Duration', value: legDurMins > 0 ? fmtMins(legDurMins) : na$1(null) },
650
+ { label: 'Airline(s)', value: na$1(l.LegAirlinesNames?.join(', ')) },
651
+ { label: 'Flight no.', value: na$1(flightNos) },
652
+ { label: 'Cabin', value: na$1(l.LegCabins?.[0]?.Name) },
653
+ { label: 'Fare brand', value: na$1(l.fareBrand?.name) },
654
+ { label: 'Supplier', value: na$1(l.supplierName) },
655
+ {
656
+ label: 'Stops',
657
+ value: stops.length === 0
658
+ ? 'Non-stop'
659
+ : stops.map((s) => `${s.StopAirportCity ?? s.StopAirport} (${na$1(s.StopAirport)}) – ${fmtMins(s.LayoverDuration)} layover`).join(' · '),
660
+ },
661
+ ],
662
+ };
663
+ }),
664
+ };
665
+ },
666
+ },
667
+ // ── PNR Info ──────────────────────────────────────────────────────────────
668
+ {
669
+ key: 'pnr_info',
670
+ type: 'flight',
671
+ label: 'PNR Info',
672
+ subtitle: 'PNR · Booking ref',
673
+ icon: 'pi pi-tag',
674
+ apiField: 'booking_info',
675
+ formatter: (data) => {
676
+ const d = bi(data);
677
+ const pnrList = resolvePnrList(d?.pnr_info);
678
+ const pnrDep = pnrList.find((p) => p.LegDirectionality === 'Departure')?.pnr ?? pnrList[0]?.pnr;
679
+ const pnrRet = pnrList.find((p) => p.LegDirectionality === 'Return')?.pnr;
680
+ const cp = d?.cancellation_policy;
681
+ const isCrossSeller = pnrList.length > 1
682
+ || cp?.supplierName?.toLowerCase().includes('cross')
683
+ || false;
684
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
685
+ const pnrRows = [];
686
+ if (pnrList.length > 1) {
687
+ pnrList.forEach((p) => {
688
+ pnrRows.push({ label: `PNR (${p.LegDirectionality})`, value: na$1(p.pnr) });
689
+ });
690
+ }
691
+ else {
692
+ pnrRows.push({ label: 'PNR', value: na$1(pnrDep) });
693
+ }
694
+ pnrRows.push({ label: 'Booking ID', value: na$1(cp?.bookingId) }, { label: 'Supplier ref', value: na$1(cp?.supplierBookingId) }, { label: 'Supplier', value: na$1(cp?.supplierName) }, { label: 'Cross-seller', value: isCrossSeller ? 'Yes' : 'No' });
695
+ return {
696
+ title: pnrDep ? `PNR: ${pnrDep}${pnrRet ? ' / ' + pnrRet : ''}` : 'PNR Info',
697
+ subtitle: isCrossSeller ? 'Cross-seller booking — two PNRs' : 'Global Distribution System reference',
698
+ sections: [{ title: 'References', rows: pnrRows }],
699
+ };
700
+ },
701
+ },
702
+ // ── Baggage Policy ────────────────────────────────────────────────────────
703
+ {
704
+ key: 'baggage',
705
+ type: 'flight',
706
+ label: 'Baggage Policy',
707
+ subtitle: 'Cabin · Checked · Add-ons',
708
+ icon: 'pi pi-briefcase',
709
+ apiField: 'booking_info',
710
+ formatter: (data) => {
711
+ const baggage = bi(data)?.baggage;
712
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
713
+ const infoArray = baggage?.baggage_info ?? [];
714
+ const addOns = baggage?.add_on_baggage ?? [];
715
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
716
+ const sections = [];
717
+ const routeNames = [];
718
+ infoArray.forEach((entry) => {
719
+ const route = Object.keys(entry)[0];
720
+ if (!route)
721
+ return;
722
+ routeNames.push(route);
723
+ const paxTypes = entry[route] ?? {};
724
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
725
+ const rows = [];
726
+ Object.entries(paxTypes).forEach(([paxType, pd]) => {
727
+ const label = paxType === 'ADT' ? 'Adult' : paxType === 'CNN' ? 'Child' : paxType === 'INF' ? 'Infant' : paxType;
728
+ const cabin = pd.CabinBaggage;
729
+ const checked = pd.CheckedBaggage;
730
+ const underSeat = pd.UnderSeatBaggage;
731
+ rows.push({ label: `${label} · Cabin`, value: fmtBag(cabin) }, { label: `${label} · Checked`, value: fmtBag(checked) });
732
+ if (underSeat) {
733
+ rows.push({ label: `${label} · Under Seat`, value: fmtBag(underSeat) });
734
+ }
735
+ });
736
+ sections.push({ title: `Baggage · ${route}`, rows });
737
+ });
738
+ if (addOns.length > 0) {
739
+ sections.push({
740
+ title: 'Add-on Baggage',
741
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
742
+ rows: addOns.map((a, i) => ({
743
+ label: `Add-on ${i + 1}`,
744
+ value: [a.weight, a.unit, a.route].filter(Boolean).join(' · ') || 'See booking details',
745
+ })),
746
+ });
747
+ }
748
+ return {
749
+ title: 'Baggage Allowance',
750
+ subtitle: routeNames.length > 0 ? routeNames.join(' · ') : 'All baggage types',
751
+ sections: sections.length > 0
752
+ ? sections
753
+ : [{ title: 'Info', rows: [{ label: 'Status', value: 'No baggage data available' }] }],
754
+ };
755
+ },
756
+ },
757
+ // ── Flight Timings ────────────────────────────────────────────────────────
758
+ {
759
+ key: 'flight_timings',
760
+ type: 'flight',
761
+ hidden: true,
762
+ label: 'Flight Timings',
763
+ subtitle: 'Departure · Arrival',
764
+ icon: 'pi pi-clock',
765
+ apiField: 'booking_info',
766
+ formatter: (data) => {
767
+ const d = bi(data);
768
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
769
+ const legs = d?.leg_info ?? [];
770
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
771
+ const airlines = d?.airline_info ?? [];
772
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
773
+ const durLegs = d?.duration?.legs ?? [];
774
+ return {
775
+ title: 'Flight Timings',
776
+ subtitle: legs.length > 0
777
+ ? `${legs[0].LegDepartureAirportCode} → ${legs[legs.length - 1].LegArrivalAirportCode}`
778
+ : 'All legs',
779
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
780
+ sections: legs.map((l, li) => {
781
+ const dir = l.LegDirectionality ?? '';
782
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
783
+ const segs = airlines[li]?.Segments ?? [];
784
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
785
+ const durSegs = durLegs[li]?.Segments ?? [];
786
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
787
+ const stops = l.Stops ?? [];
788
+ const legDurMins = durSegs.reduce((sum, s) => sum + (s.SegmentElapsedTimeMinutes ?? 0), 0);
789
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
790
+ const rows = [
791
+ { label: 'Departure', value: fmtDate(l.LegDepartureDateTime) },
792
+ { label: 'Arrival', value: fmtDate(l.LegArrivalDateTime) },
793
+ { label: 'Duration', value: legDurMins > 0 ? fmtMins(legDurMins) : na$1(null) },
794
+ ];
795
+ // Interleave segment rows with layover rows
796
+ segs.forEach((s, si) => {
797
+ const segDur = durSegs[si]?.SegmentElapsedTimeMinutes;
798
+ rows.push({
799
+ label: `Seg ${si + 1} · ${s.AirlineCode}${s.FlightNumber}`,
800
+ value: `${na$1(s.DepartureAirportCode)} → ${na$1(s.ArrivalAirportCode)}${segDur ? ' · ' + fmtMins(segDur) : ''}`,
801
+ });
802
+ // Layover after this segment (if there is a stop at arrival airport)
803
+ if (si < segs.length - 1) {
804
+ const stop = stops.find((st) => st.StopAirport === s.ArrivalAirportCode);
805
+ if (stop) {
806
+ rows.push({ label: `Layover · ${stop.StopAirport}`, value: fmtMins(stop.LayoverDuration) });
807
+ }
808
+ }
809
+ });
810
+ return {
811
+ title: `${dirIcon(dir)} ${dir || `Leg ${li + 1}`} · ${l.LegDepartureAirportCode} → ${l.LegArrivalAirportCode}`,
812
+ rows,
813
+ };
814
+ }),
815
+ };
816
+ },
817
+ },
818
+ // ── Duration ──────────────────────────────────────────────────────────────
819
+ {
820
+ key: 'duration',
821
+ type: 'flight',
822
+ hidden: true,
823
+ label: 'Duration',
824
+ subtitle: 'Total flight time',
825
+ icon: 'pi pi-refresh',
826
+ apiField: 'booking_info',
827
+ formatter: (data) => {
828
+ const d = bi(data);
829
+ const dur = d?.duration;
830
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
831
+ const legs = d?.leg_info ?? [];
832
+ const total = fmtMins(dur?.totalMinutes);
833
+ return {
834
+ title: total,
835
+ subtitle: 'Total journey duration including layovers',
836
+ sections: [
837
+ {
838
+ title: 'Total',
839
+ rows: [{ label: 'Journey duration', value: total }],
840
+ },
841
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
842
+ ...(dur?.legs ?? []).map((leg, li) => {
843
+ const legMeta = legs[li];
844
+ const dir = legMeta?.LegDirectionality ?? '';
845
+ const from = legMeta?.LegDepartureAirportCode;
846
+ const to = legMeta?.LegArrivalAirportCode;
847
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
848
+ const rows = [];
849
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
850
+ (leg.Segments ?? []).forEach((s) => {
851
+ rows.push({ label: `${s.DepartureAirportCode} → ${s.ArrivalAirportCode}`, value: fmtMins(s.SegmentElapsedTimeMinutes) });
852
+ });
853
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
854
+ (leg.Stops ?? []).forEach((s) => {
855
+ rows.push({ label: `Layover · ${s.StopAirport}`, value: fmtMins(s.LayoverDuration) });
856
+ });
857
+ return {
858
+ title: `${dirIcon(dir)} ${dir || `Leg ${li + 1}`}${from && to ? ` · ${from} → ${to}` : ''}`,
859
+ rows,
860
+ };
861
+ }),
862
+ ],
863
+ };
864
+ },
865
+ },
866
+ // ── Cancellation Policy ───────────────────────────────────────────────────
867
+ {
868
+ key: 'cancellation_policy',
869
+ type: 'flight',
870
+ label: 'Cancellation Policy',
871
+ subtitle: 'Refund · Fees · Legs',
872
+ icon: 'pi pi-ban',
873
+ apiField: 'booking_info',
874
+ formatter: (data) => {
875
+ const d = bi(data);
876
+ const cp = d?.cancellation_policy;
877
+ if (!cp) {
878
+ return {
879
+ title: 'Cancellation Policy',
880
+ subtitle: 'No data available',
881
+ sections: [{ title: 'Info', rows: [{ label: 'Status', value: 'No cancellation policy data available' }] }],
882
+ };
883
+ }
884
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
885
+ const sections = [];
886
+ const pnrList = resolvePnrList(d?.pnr_info);
887
+ const isCrossSeller = pnrList.length > 1 || cp?.supplierName?.toLowerCase().includes('cross');
888
+ const legs = cp?.legs ?? [];
889
+ sections.push({
890
+ title: 'Booking Reference',
891
+ rows: [
892
+ { label: 'Booking ID', value: na$1(cp.bookingId) },
893
+ { label: 'Supplier ref', value: na$1(cp.supplierBookingId) },
894
+ { label: 'Supplier', value: na$1(cp.supplierName) },
895
+ { label: 'Cross-seller', value: isCrossSeller ? 'Yes' : 'No' },
896
+ ],
897
+ });
898
+ const fmtFee = (fee, currency) => fee != null ? `${fee} ${currency ?? 'SAR'}` : '0';
899
+ legs.forEach((leg) => {
900
+ const title = `Cancellation and Change Policy · ${leg.direction} (${leg.from} → ${leg.to})`;
901
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
902
+ const rows = [];
903
+ (leg.passengerPenalties ?? []).forEach((pp) => {
904
+ const typeLabel = pp.type === 'ADT' ? 'Adult' : pp.type === 'CNN' ? 'Child' : 'Infant';
905
+ const bDep = pp.penalties?.beforeDeparture;
906
+ const aDep = pp.penalties?.afterDeparture;
907
+ const bRefund = bDep?.refund;
908
+ const bExchange = bDep?.exchange;
909
+ const aRefund = aDep?.refund;
910
+ const aExchange = aDep?.exchange;
911
+ rows.push({
912
+ label: `${typeLabel} · Before dep.`,
913
+ value: [
914
+ bRefund?.isActionable ? `Refund: ${fmtFee(bRefund.fee, bRefund.currency)}` : 'No refund',
915
+ bExchange?.isActionable ? `Exchange: ${fmtFee(bExchange.fee, bExchange.currency)}` : 'No exchange',
916
+ ].join(' · '),
917
+ valueClass: bRefund?.isActionable || bExchange?.isActionable ? 'neutral' : 'negative',
918
+ }, {
919
+ label: `${typeLabel} · After dep.`,
920
+ value: [
921
+ aRefund?.isActionable ? `Refund: ${fmtFee(aRefund.fee, aRefund.currency)}` : 'No refund',
922
+ aExchange?.isActionable ? `Exchange: ${fmtFee(aExchange.fee, aExchange.currency)}` : 'No exchange',
923
+ ].join(' · '),
924
+ valueClass: aRefund?.isActionable || aExchange?.isActionable ? 'neutral' : 'negative',
925
+ });
926
+ });
927
+ sections.push({ title, rows });
928
+ });
929
+ return {
930
+ title: `${cp.supplierName ?? 'Cancellation'} Policy`,
931
+ subtitle: isCrossSeller ? 'Cross-seller booking · contact supplier for refund' : `Booking ${cp.bookingId ?? ''}`,
932
+ sections,
933
+ callout: isCrossSeller
934
+ ? { type: 'info', message: 'Cross-seller booking — refund is processed by the partner and may take additional time.' }
935
+ : undefined,
936
+ };
937
+ },
938
+ },
939
+ // ── Flight Status ─────────────────────────────────────────────────────────
940
+ {
941
+ key: 'flight_status',
942
+ type: 'flight',
943
+ hidden: true,
944
+ label: 'Flight Status',
945
+ subtitle: 'Live · Delays',
946
+ icon: 'pi pi-chart-bar',
947
+ apiField: 'booking_info',
948
+ formatter: (data) => {
949
+ const d = bi(data);
950
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
951
+ const legs = d?.leg_info ?? [];
952
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
953
+ const airlines = d?.airline_info ?? [];
954
+ const origin = legs[0]?.LegDepartureAirportCode;
955
+ const dest = legs[legs.length - 1]?.LegArrivalAirportCode;
956
+ return {
957
+ title: origin ? `${origin} → ${dest}` : 'Flight Status',
958
+ subtitle: `${legs.length} leg${legs.length !== 1 ? 's' : ''} · Scheduled times from booking`,
959
+ sections: legs.map((l, li) => {
960
+ const dir = l.LegDirectionality ?? '';
961
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
962
+ const segs = airlines[li]?.Segments ?? [];
963
+ return {
964
+ title: `${dirIcon(dir)} ${dir || `Leg ${li + 1}`} · ${l.LegDepartureAirportCode} → ${l.LegArrivalAirportCode}`,
965
+ rows: [
966
+ { label: 'Departure', value: fmtDate(l.LegDepartureDateTime) },
967
+ { label: 'Arrival', value: fmtDate(l.LegArrivalDateTime) },
968
+ { label: 'Airline(s)', value: na$1(l.LegAirlinesNames?.join(', ')) },
969
+ { label: 'Cabin', value: na$1(l.LegCabins?.[0]?.Name) },
970
+ { label: 'Fare brand', value: na$1(l.fareBrand?.name) },
971
+ ...segs.map((s, si) => ({
972
+ label: `Flight ${si + 1}`,
973
+ value: `${s.AirlineCode}${s.FlightNumber} · ${s.DepartureAirportCode} → ${s.ArrivalAirportCode}`,
974
+ })),
975
+ ],
976
+ };
977
+ }),
978
+ callout: { type: 'info', message: 'Live status not available — check the airline app or airport boards for real-time updates.' },
979
+ };
980
+ },
981
+ },
982
+ // ── Wi-Fi & Extras ────────────────────────────────────────────────────────
983
+ {
984
+ key: 'wifi',
985
+ type: 'flight',
986
+ hidden: true,
987
+ label: 'Wi-Fi & Extras',
988
+ subtitle: 'Onboard services',
989
+ icon: 'pi pi-info-circle',
990
+ apiField: 'booking_info',
991
+ formatter: (data) => {
992
+ const d = bi(data);
993
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
994
+ const legs = d?.leg_info ?? [];
995
+ const addOns = d?.baggage?.add_on_baggage ?? [];
996
+ return {
997
+ title: 'Onboard Extras',
998
+ subtitle: `${legs.length} leg${legs.length !== 1 ? 's' : ''}`,
999
+ sections: [
1000
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1001
+ ...legs.map((l, li) => {
1002
+ const dir = l.LegDirectionality ?? '';
1003
+ return {
1004
+ title: `${dirIcon(dir)} ${dir || `Leg ${li + 1}`} · ${l.LegDepartureAirportCode} → ${l.LegArrivalAirportCode}`,
1005
+ rows: [
1006
+ { label: 'Cabin', value: na$1(l.LegCabins?.[0]?.Name) },
1007
+ { label: 'Fare brand', value: na$1(l.fareBrand?.name) },
1008
+ { label: 'Airline(s)', value: na$1(l.LegAirlinesNames?.join(', ')) },
1009
+ ],
1010
+ };
1011
+ }),
1012
+ ...(addOns.length > 0 ? [{
1013
+ title: 'Add-on Baggage',
1014
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1015
+ rows: addOns.map((a, i) => ({
1016
+ label: `Add-on ${i + 1}`,
1017
+ value: [a.weight, a.unit, a.route].filter(Boolean).join(' · ') || 'See booking details',
1018
+ })),
1019
+ }] : []),
1020
+ ],
1021
+ callout: { type: 'info', message: 'Detailed onboard amenities depend on the operating airline and fare class.' },
1022
+ };
1023
+ },
1024
+ },
1025
+ ];
1026
+
1027
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1028
+ const na = (v) => v ?? 'Not available';
1029
+ const HOTEL_ACTIONS = [
1030
+ { key: 'booking_info', type: 'hotel', label: 'Booking Overview', subtitle: 'Summary · Status', icon: 'pi pi-file',
1031
+ apiField: 'booking_info',
1032
+ formatter: (data) => ({ title: `Booking ${na(data?.booking_info?.reference)}`, subtitle: `Created ${na(data?.booking_info?.created_at)}`,
1033
+ points: [{ label: 'Status', value: na(data?.booking_info?.status) }, { label: 'Total paid', value: na(data?.booking_info?.total) }, { label: 'Source', value: na(data?.booking_info?.source) }, { label: 'Currency', value: na(data?.booking_info?.currency) }] }) },
1034
+ { key: 'room_information', type: 'hotel', label: 'Room Information', subtitle: 'Type · Beds · View', icon: 'pi pi-home',
1035
+ apiField: 'room_information',
1036
+ formatter: (data) => ({ title: na(data?.room_information?.type), subtitle: na(data?.room_information?.description),
1037
+ points: [{ label: 'Room type', value: na(data?.room_information?.type) }, { label: 'Beds', value: na(data?.room_information?.beds) }, { label: 'View', value: na(data?.room_information?.view) }, { label: 'Floor', value: na(data?.room_information?.floor) }, { label: 'Size', value: na(data?.room_information?.size) }] }) },
1038
+ { key: 'checkin_checkout', type: 'hotel', label: 'Check-in / Check-out', subtitle: 'Dates · Times', icon: 'pi pi-calendar',
1039
+ apiField: 'checkin_checkout',
1040
+ formatter: (data) => ({ title: `${na(data?.checkin_checkout?.checkin_date)} → ${na(data?.checkin_checkout?.checkout_date)}`, subtitle: 'Stay dates and times',
1041
+ points: [{ label: 'Check-in date', value: na(data?.checkin_checkout?.checkin_date) }, { label: 'Check-in time', value: na(data?.checkin_checkout?.checkin_time) }, { label: 'Check-out date', value: na(data?.checkin_checkout?.checkout_date) }, { label: 'Check-out time', value: na(data?.checkin_checkout?.checkout_time) }, { label: 'Nights', value: na(data?.checkin_checkout?.nights) }] }) },
1042
+ { key: 'hotel_information', type: 'hotel', label: 'Hotel Information', subtitle: 'Name · Stars · Contact', icon: 'pi pi-building',
1043
+ apiField: 'hotel_information',
1044
+ formatter: (data) => ({ title: na(data?.hotel_information?.name), subtitle: `${na(data?.hotel_information?.stars)}★ · ${na(data?.hotel_information?.city)}`,
1045
+ points: [{ label: 'Hotel', value: na(data?.hotel_information?.name) }, { label: 'Stars', value: na(data?.hotel_information?.stars) }, { label: 'Phone', value: na(data?.hotel_information?.phone) }, { label: 'Email', value: na(data?.hotel_information?.email) }, { label: 'Website', value: na(data?.hotel_information?.website) }] }) },
1046
+ { key: 'location_info', type: 'hotel', label: 'Location Info', subtitle: 'Address · Map', icon: 'pi pi-map-marker',
1047
+ apiField: 'location_info',
1048
+ formatter: (data) => ({ title: na(data?.location_info?.address), subtitle: na(data?.location_info?.city),
1049
+ points: [{ label: 'Address', value: na(data?.location_info?.address) }, { label: 'City', value: na(data?.location_info?.city) }, { label: 'Country', value: na(data?.location_info?.country) }, { label: 'Zip code', value: na(data?.location_info?.zip) }, { label: 'Landmark', value: na(data?.location_info?.landmark) }] }) },
1050
+ { key: 'hotel_amenities', type: 'hotel', label: 'Hotel Amenities', subtitle: 'Pool · Gym · Wi-Fi', icon: 'pi pi-star',
1051
+ apiField: 'hotel_amenities',
1052
+ formatter: (data) => { const amenities = data?.hotel_amenities?.list ?? []; return { title: 'Amenities', subtitle: `${amenities.length} amenities available`, points: amenities.length > 0 ? amenities.map((a) => ({ label: '✓', value: a })) : [{ label: 'Amenities', value: 'Not available' }] }; } },
1053
+ { key: 'nearby_places', type: 'hotel', label: 'Nearby Places', subtitle: 'Airport · Attractions', icon: 'pi pi-compass',
1054
+ apiField: 'nearby_places',
1055
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1056
+ formatter: (data) => { const places = data?.nearby_places?.places ?? []; return { title: 'Nearby Places', points: places.length > 0 ? places.flatMap((p) => [{ label: na(p?.type), value: `${na(p?.name)} (${na(p?.distance)})` }]) : [{ label: 'Places', value: 'Not available' }] }; } },
1057
+ { key: 'experiences', type: 'hotel', label: 'Experiences', subtitle: 'Tours · Activities', icon: 'pi pi-star-fill',
1058
+ apiField: 'experiences',
1059
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1060
+ formatter: (data) => { const items = data?.experiences?.list ?? []; return { title: 'Available Experiences', points: items.length > 0 ? items.map((e) => ({ label: na(e?.name), value: na(e?.price) })) : [{ label: 'Experiences', value: 'Not available' }] }; } },
1061
+ { key: 'payment_status', type: 'hotel', label: 'Payment Status', subtitle: 'Charges · Receipt', icon: 'pi pi-credit-card',
1062
+ apiField: 'payment_status',
1063
+ formatter: (data) => ({ title: 'Payment Details', subtitle: na(data?.payment_status?.method),
1064
+ points: [{ label: 'Status', value: na(data?.payment_status?.status) }, { label: 'Amount paid', value: na(data?.payment_status?.amount) }, { label: 'Currency', value: na(data?.payment_status?.currency) }, { label: 'Transaction', value: na(data?.payment_status?.transaction_id) }, { label: 'Date', value: na(data?.payment_status?.date) }] }) },
1065
+ { key: 'wallet', type: 'hotel', label: 'Wallet & Credits', subtitle: 'Balance · Cashback', icon: 'pi pi-wallet',
1066
+ apiField: 'wallet',
1067
+ formatter: (data) => ({ title: 'Wallet Summary',
1068
+ points: [{ label: 'Balance', value: na(data?.wallet?.balance) }, { label: 'Cashback', value: na(data?.wallet?.cashback) }, { label: 'Promo credit', value: na(data?.wallet?.promo_credit) }, { label: 'Last updated', value: na(data?.wallet?.updated_at) }] }) },
1069
+ { key: 'cancellation_policy', type: 'hotel', label: 'Cancellation Policy', subtitle: 'Fees · Deadline', icon: 'pi pi-ban',
1070
+ apiField: 'cancellation_policy',
1071
+ formatter: (data) => ({ title: 'Cancellation Rules', subtitle: data?.cancellation_policy?.refundable ? 'Refundable' : 'Non-refundable',
1072
+ points: [{ label: 'Refundable', value: data?.cancellation_policy?.refundable ? 'Yes' : 'No' }, { label: 'Cancel fee', value: na(data?.cancellation_policy?.fee) }, { label: 'Deadline', value: na(data?.cancellation_policy?.deadline) }, { label: 'Policy', value: na(data?.cancellation_policy?.description) }],
1073
+ callout: { type: 'warning', message: data?.cancellation_policy?.warning ?? 'Always verify cancellation terms with the hotel.' } }) },
1074
+ { key: 'breakfast', type: 'hotel', label: 'Meals & Breakfast', subtitle: 'Board basis', icon: 'pi pi-list',
1075
+ apiField: 'meals',
1076
+ formatter: (data) => ({ title: na(data?.meals?.board_basis), subtitle: 'Meal inclusions',
1077
+ points: [{ label: 'Board basis', value: na(data?.meals?.board_basis) }, { label: 'Breakfast', value: na(data?.meals?.breakfast) }, { label: 'Lunch', value: na(data?.meals?.lunch) }, { label: 'Dinner', value: na(data?.meals?.dinner) }] }) },
1078
+ ];
1079
+
1080
+ class SbdBookingInputComponent {
1081
+ constructor() {
1082
+ this.loading = input(false);
1083
+ this.product = model('flight');
1084
+ this.bookingId = model('');
1085
+ this.lookup = output();
1086
+ this.productOptions = [
1087
+ { label: '✈ Flight', value: 'flight' },
1088
+ ];
1089
+ this.demoIds = [
1090
+ { label: 'Demo Flight: ALM-20481', value: 'ALM-20481' },
1091
+ { label: 'Demo Hotel: ALM-HTL-9921', value: 'ALM-HTL-9921' },
1092
+ ];
1093
+ }
1094
+ fillDemo(id) {
1095
+ const product = id.includes('HTL') ? 'hotel' : 'flight';
1096
+ this.product.set(product);
1097
+ this.bookingId.set(id);
1098
+ }
1099
+ onKeydown(event) {
1100
+ if (event.key === 'Enter')
1101
+ this.lookup.emit();
1102
+ }
1103
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdBookingInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1104
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.2.14", type: SbdBookingInputComponent, isStandalone: true, selector: "app-sbd-booking-input", inputs: { loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, product: { classPropertyName: "product", publicName: "product", isSignal: true, isRequired: false, transformFunction: null }, bookingId: { classPropertyName: "bookingId", publicName: "bookingId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { product: "productChange", bookingId: "bookingIdChange", lookup: "lookup" }, ngImport: i0, template: "<div class=\"sbd-booking-input\">\n <p-selectButton\n [(ngModel)]=\"product\"\n [options]=\"productOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n optionDisabled=\"disabled\">\n <ng-template pTemplate=\"item\" let-item>\n <span\n [pTooltip]=\"item.disabled ? 'Coming soon' : null\"\n tooltipPosition=\"bottom\"\n style=\"pointer-events: auto; display: inline-block;\">\n {{ item.label }}\n </span>\n </ng-template>\n </p-selectButton>\n <div class=\"sbd-booking-input__row\">\n <div class=\"sbd-booking-input__field-wrap\">\n <i class=\"pi pi-search sbd-booking-input__icon\"></i>\n <input pInputText class=\"sbd-booking-input__field\" placeholder=\"Enter Booking ID\"\n [(ngModel)]=\"bookingId\" (keydown)=\"onKeydown($event)\" autocomplete=\"off\" spellcheck=\"false\" />\n </div>\n <button pButton label=\"Lookup \u2192\" [disabled]=\"!bookingId().trim()\" (click)=\"lookup.emit()\" class=\"sbd-booking-input__btn\"></button>\n </div>\n</div>\n", styles: [".sbd-booking-input{background:#fff;border-radius:12px;border:1px solid #e2e8f0;padding:20px;display:flex;flex-direction:column;gap:14px}.sbd-booking-input__row{display:flex;gap:10px;align-items:center}.sbd-booking-input__field-wrap{flex:1;position:relative;display:flex;align-items:center}.sbd-booking-input__icon{position:absolute;left:12px;color:#94a3b8;font-size:14px;pointer-events:none;z-index:1}.sbd-booking-input__field{width:100%!important;padding-left:36px!important;height:44px;font-size:14px!important}.sbd-booking-input__btn{height:44px;white-space:nowrap}.sbd-booking-input__demos{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.sbd-booking-input__demos-label{font-size:11.5px;color:#94a3b8}.sbd-booking-input__chip{padding:4px 10px;border-radius:20px;border:1px solid #e2e8f0;background:#f8fafc;color:#1e2e4a;font-size:11.5px;font-weight:500;cursor:pointer;transition:border-color .15s,background .15s}.sbd-booking-input__chip:hover{border-color:#3b82f6;background:#eff6ff;color:#3b82f6}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.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: SelectButtonModule }, { kind: "component", type: i2.SelectButton, selector: "p-selectButton", inputs: ["options", "optionLabel", "optionValue", "optionDisabled", "tabindex", "multiple", "style", "styleClass", "ariaLabelledBy", "disabled", "dataKey"], outputs: ["onOptionClick", "onChange"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i3.InputText, selector: "[pInputText]" }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i3$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"] }] }); }
1105
+ }
1106
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdBookingInputComponent, decorators: [{
1107
+ type: Component,
1108
+ args: [{ selector: 'app-sbd-booking-input', standalone: true, imports: [FormsModule, SelectButtonModule, InputTextModule, ButtonModule, TooltipModule], template: "<div class=\"sbd-booking-input\">\n <p-selectButton\n [(ngModel)]=\"product\"\n [options]=\"productOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n optionDisabled=\"disabled\">\n <ng-template pTemplate=\"item\" let-item>\n <span\n [pTooltip]=\"item.disabled ? 'Coming soon' : null\"\n tooltipPosition=\"bottom\"\n style=\"pointer-events: auto; display: inline-block;\">\n {{ item.label }}\n </span>\n </ng-template>\n </p-selectButton>\n <div class=\"sbd-booking-input__row\">\n <div class=\"sbd-booking-input__field-wrap\">\n <i class=\"pi pi-search sbd-booking-input__icon\"></i>\n <input pInputText class=\"sbd-booking-input__field\" placeholder=\"Enter Booking ID\"\n [(ngModel)]=\"bookingId\" (keydown)=\"onKeydown($event)\" autocomplete=\"off\" spellcheck=\"false\" />\n </div>\n <button pButton label=\"Lookup \u2192\" [disabled]=\"!bookingId().trim()\" (click)=\"lookup.emit()\" class=\"sbd-booking-input__btn\"></button>\n </div>\n</div>\n", styles: [".sbd-booking-input{background:#fff;border-radius:12px;border:1px solid #e2e8f0;padding:20px;display:flex;flex-direction:column;gap:14px}.sbd-booking-input__row{display:flex;gap:10px;align-items:center}.sbd-booking-input__field-wrap{flex:1;position:relative;display:flex;align-items:center}.sbd-booking-input__icon{position:absolute;left:12px;color:#94a3b8;font-size:14px;pointer-events:none;z-index:1}.sbd-booking-input__field{width:100%!important;padding-left:36px!important;height:44px;font-size:14px!important}.sbd-booking-input__btn{height:44px;white-space:nowrap}.sbd-booking-input__demos{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.sbd-booking-input__demos-label{font-size:11.5px;color:#94a3b8}.sbd-booking-input__chip{padding:4px 10px;border-radius:20px;border:1px solid #e2e8f0;background:#f8fafc;color:#1e2e4a;font-size:11.5px;font-weight:500;cursor:pointer;transition:border-color .15s,background .15s}.sbd-booking-input__chip:hover{border-color:#3b82f6;background:#eff6ff;color:#3b82f6}\n"] }]
1109
+ }] });
1110
+
1111
+ class SbdWorkflowActionsComponent {
1112
+ constructor() {
1113
+ this.bookingId = input.required();
1114
+ this.cancelBooking = output();
1115
+ }
1116
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdWorkflowActionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1117
+ 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: i3$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"] }] }); }
1118
+ }
1119
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdWorkflowActionsComponent, decorators: [{
1120
+ type: Component,
1121
+ 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"] }]
1122
+ }] });
1123
+
1124
+ class SbdActionCardComponent {
1125
+ constructor() {
1126
+ this.action = input.required();
1127
+ this.active = input(false);
1128
+ this.loading = input(false);
1129
+ this.selected = output();
1130
+ }
1131
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdActionCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1132
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SbdActionCardComponent, isStandalone: true, selector: "app-sbd-action-card", inputs: { action: { classPropertyName: "action", publicName: "action", isSignal: true, isRequired: true, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selected" }, ngImport: i0, template: "<button class=\"sbd-action-card\" [class.sbd-action-card--active]=\"active()\"\n [class.sbd-action-card--loading]=\"loading()\" [pTooltip]=\"action().subtitle\"\n tooltipPosition=\"top\" (click)=\"selected.emit(action().key)\" type=\"button\">\n @if (active()) { <span class=\"sbd-action-card__dot\"></span> }\n <div class=\"sbd-action-card__icon-tile\">\n <i [class]=\"action().icon\"></i>\n </div>\n <div class=\"sbd-action-card__body\">\n <span class=\"sbd-action-card__label\">{{ action().label }}</span>\n <span class=\"sbd-action-card__sub\">{{ action().subtitle }}</span>\n </div>\n</button>\n", styles: [".sbd-action-card{position:relative;display:flex;flex-direction:column;align-items:flex-start;gap:10px;padding:14px;background:#fff;border:1.5px solid #e2e8f0;border-radius:10px;cursor:pointer;text-align:left;width:100%;transition:transform .15s,box-shadow .15s,border-color .15s;font-family:inherit}.sbd-action-card:hover{transform:translateY(-2px);box-shadow:0 4px 16px #00000014;border-color:#1e2e4a}.sbd-action-card--active{border-color:#3b82f6!important;background:#eff6ff!important}.sbd-action-card--active .sbd-action-card__icon-tile{background:#3b82f61f;color:#3b82f6}.sbd-action-card--active .sbd-action-card__label{color:#3b82f6}.sbd-action-card--loading{pointer-events:none;opacity:.7}.sbd-action-card__dot{position:absolute;top:8px;right:8px;width:7px;height:7px;border-radius:50%;background:#3b82f6}.sbd-action-card__icon-tile{width:38px;height:38px;border-radius:8px;background:#f1f5f9;color:#1e2e4a;display:flex;align-items:center;justify-content:center;flex-shrink:0}.sbd-action-card__icon-tile i{font-size:18px}.sbd-action-card__body{display:flex;flex-direction:column;gap:2px;min-width:0;width:100%}.sbd-action-card__label{font-size:12.5px;font-weight:700;color:#1e2e4a;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sbd-action-card__sub{font-size:11px;color:#94a3b8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"], dependencies: [{ 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"] }] }); }
1133
+ }
1134
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdActionCardComponent, decorators: [{
1135
+ type: Component,
1136
+ args: [{ selector: 'app-sbd-action-card', standalone: true, imports: [TooltipModule], template: "<button class=\"sbd-action-card\" [class.sbd-action-card--active]=\"active()\"\n [class.sbd-action-card--loading]=\"loading()\" [pTooltip]=\"action().subtitle\"\n tooltipPosition=\"top\" (click)=\"selected.emit(action().key)\" type=\"button\">\n @if (active()) { <span class=\"sbd-action-card__dot\"></span> }\n <div class=\"sbd-action-card__icon-tile\">\n <i [class]=\"action().icon\"></i>\n </div>\n <div class=\"sbd-action-card__body\">\n <span class=\"sbd-action-card__label\">{{ action().label }}</span>\n <span class=\"sbd-action-card__sub\">{{ action().subtitle }}</span>\n </div>\n</button>\n", styles: [".sbd-action-card{position:relative;display:flex;flex-direction:column;align-items:flex-start;gap:10px;padding:14px;background:#fff;border:1.5px solid #e2e8f0;border-radius:10px;cursor:pointer;text-align:left;width:100%;transition:transform .15s,box-shadow .15s,border-color .15s;font-family:inherit}.sbd-action-card:hover{transform:translateY(-2px);box-shadow:0 4px 16px #00000014;border-color:#1e2e4a}.sbd-action-card--active{border-color:#3b82f6!important;background:#eff6ff!important}.sbd-action-card--active .sbd-action-card__icon-tile{background:#3b82f61f;color:#3b82f6}.sbd-action-card--active .sbd-action-card__label{color:#3b82f6}.sbd-action-card--loading{pointer-events:none;opacity:.7}.sbd-action-card__dot{position:absolute;top:8px;right:8px;width:7px;height:7px;border-radius:50%;background:#3b82f6}.sbd-action-card__icon-tile{width:38px;height:38px;border-radius:8px;background:#f1f5f9;color:#1e2e4a;display:flex;align-items:center;justify-content:center;flex-shrink:0}.sbd-action-card__icon-tile i{font-size:18px}.sbd-action-card__body{display:flex;flex-direction:column;gap:2px;min-width:0;width:100%}.sbd-action-card__label{font-size:12.5px;font-weight:700;color:#1e2e4a;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sbd-action-card__sub{font-size:11px;color:#94a3b8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"] }]
1137
+ }] });
1138
+
1139
+ class SbdActionGridComponent {
1140
+ constructor() {
1141
+ this.actions = input.required();
1142
+ this.activeKey = input(null);
1143
+ this.loadingKey = input(null);
1144
+ this.actionSelected = output();
1145
+ }
1146
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdActionGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1147
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SbdActionGridComponent, isStandalone: true, selector: "app-sbd-action-grid", inputs: { actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: true, transformFunction: null }, activeKey: { classPropertyName: "activeKey", publicName: "activeKey", isSignal: true, isRequired: false, transformFunction: null }, loadingKey: { classPropertyName: "loadingKey", publicName: "loadingKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { actionSelected: "actionSelected" }, ngImport: i0, template: "<div class=\"sbd-action-grid\">\n <div class=\"sbd-action-grid__header\">\n <h3 class=\"sbd-action-grid__title\">Booking Details</h3>\n </div>\n <div class=\"sbd-action-grid__grid\">\n @for (action of actions(); track action.key) {\n <app-sbd-action-card [action]=\"action\" [active]=\"activeKey() === action.key\"\n [loading]=\"loadingKey() === action.key\" (selected)=\"actionSelected.emit($event)\" />\n }\n </div>\n</div>\n", styles: [".sbd-action-grid{background:#fff;border-radius:12px;border:1px solid #e2e8f0;padding:20px}.sbd-action-grid__header{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}.sbd-action-grid__title{font-size:13.5px;font-weight:700;color:#1e2e4a;margin:0}.sbd-action-grid__count{font-size:11.5px;color:#94a3b8;background:#f8fafc;border:1px solid #e2e8f0;border-radius:20px;padding:2px 10px}.sbd-action-grid__grid{display:grid;grid-template-columns:repeat(5,1fr);gap:10px}@media (max-width: 1200px){.sbd-action-grid__grid{grid-template-columns:repeat(4,1fr)}}@media (max-width: 900px){.sbd-action-grid__grid{grid-template-columns:repeat(3,1fr)}}@media (max-width: 640px){.sbd-action-grid__grid{grid-template-columns:repeat(2,1fr)}}\n"], dependencies: [{ kind: "component", type: SbdActionCardComponent, selector: "app-sbd-action-card", inputs: ["action", "active", "loading"], outputs: ["selected"] }] }); }
1148
+ }
1149
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdActionGridComponent, decorators: [{
1150
+ type: Component,
1151
+ args: [{ selector: 'app-sbd-action-grid', standalone: true, imports: [SbdActionCardComponent], template: "<div class=\"sbd-action-grid\">\n <div class=\"sbd-action-grid__header\">\n <h3 class=\"sbd-action-grid__title\">Booking Details</h3>\n </div>\n <div class=\"sbd-action-grid__grid\">\n @for (action of actions(); track action.key) {\n <app-sbd-action-card [action]=\"action\" [active]=\"activeKey() === action.key\"\n [loading]=\"loadingKey() === action.key\" (selected)=\"actionSelected.emit($event)\" />\n }\n </div>\n</div>\n", styles: [".sbd-action-grid{background:#fff;border-radius:12px;border:1px solid #e2e8f0;padding:20px}.sbd-action-grid__header{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}.sbd-action-grid__title{font-size:13.5px;font-weight:700;color:#1e2e4a;margin:0}.sbd-action-grid__count{font-size:11.5px;color:#94a3b8;background:#f8fafc;border:1px solid #e2e8f0;border-radius:20px;padding:2px 10px}.sbd-action-grid__grid{display:grid;grid-template-columns:repeat(5,1fr);gap:10px}@media (max-width: 1200px){.sbd-action-grid__grid{grid-template-columns:repeat(4,1fr)}}@media (max-width: 900px){.sbd-action-grid__grid{grid-template-columns:repeat(3,1fr)}}@media (max-width: 640px){.sbd-action-grid__grid{grid-template-columns:repeat(2,1fr)}}\n"] }]
1152
+ }] });
1153
+
1154
+ const DEDUCTION_DEFS = [
1155
+ { key: 'totalPenaltyPoints', label: 'Airline Cancellation Fee', badge: '' },
1156
+ { key: 'handlingFee', label: 'Handling Fee', badge: '' },
1157
+ { key: 'totalNonRefundableServiceCharge', label: 'Service Charge', badge: '' },
1158
+ { key: 'totalAncillaryRefundAmount', label: 'Ancillary Fee', badge: '' },
1159
+ { key: 'totalGiftAmount', label: 'Add on Fee', badge: '' },
1160
+ // `totalLoyaltyPoints` (points spent at booking time, now forfeited) is
1161
+ // intentionally not listed here — it's already surfaced under its own
1162
+ // program name (e.g. "Qitaf") in the "Total Paid" breakdown, so listing it
1163
+ // again here would double up the same amount under two different sections.
1164
+ ];
1165
+ const REFUND_DEFS = [
1166
+ { key: 'totalCardRefundAmount', label: 'Refund Amount' },
1167
+ { key: 'totalCreditRefundAmount', label: 'Credit Shell Refund' },
1168
+ { key: 'walletRefundAmount', label: 'Almatar Wallet' },
1169
+ { key: 'totalLoyaltyPointsRefundAmount', label: 'Loyalty Points Refund' },
1170
+ ];
1171
+ /** Labels for `paymentMethodCustomerUsed` gateway codes returned by the backend. */
1172
+ const PAYMENT_GATEWAY_LABELS = {
1173
+ CH: 'Card',
1174
+ HP: 'Hyperpay',
1175
+ MF: 'My Fatoora',
1176
+ WT: 'Wire Transfer',
1177
+ STC: 'STC',
1178
+ TM: 'Tamara',
1179
+ TB: 'Tabby',
1180
+ RAJHI: 'Al Rajhi Mokafaa',
1181
+ PPB: 'Customer Balance',
1182
+ PP: 'Postpay',
1183
+ TAMAM: 'Tamam',
1184
+ QITAF: 'Qitaf',
1185
+ MIS: 'MIS Pay',
1186
+ MATAR: 'Almatar Pay',
1187
+ CB: 'Company Balance',
1188
+ ANB: 'ANB',
1189
+ AMWAL: 'Amwal',
1190
+ WALA: 'Wala',
1191
+ YGG: 'YGG',
1192
+ };
1193
+ /** Gateway codes that refund to an installment plan rather than the original card. */
1194
+ const INSTALLMENT_GATEWAY_CODES = ['TM', 'TB'];
1195
+ /** Gateway codes that refund to a loyalty-program account rather than the original card. */
1196
+ const LOYALTY_GATEWAY_CODES = ['RAJHI', 'QITAF', 'STC', 'WALA'];
1197
+ /**
1198
+ * Internal payment-processor/gateway names, never customer-recognizable
1199
+ * brands — must not be surfaced verbatim (e.g. a card refund processed
1200
+ * through Hyperpay must read as "Card"/"Original Payment Method", never
1201
+ * "Hyperpay"). Callers fall back to a generic label whenever the normalized
1202
+ * lookup returns ''.
1203
+ */
1204
+ const INTERNAL_GATEWAY_CODES = ['HP', 'MF', 'WT', 'PPB', 'PP', 'TAMAM'];
1205
+ /**
1206
+ * Result/confirmation-screen-only override: these gateways process card
1207
+ * payments through Mada — shown specifically as "Mada Card" instead of
1208
+ * falling back to the generic "Original Payment Method" label used
1209
+ * everywhere else `INTERNAL_GATEWAY_CODES` applies.
1210
+ */
1211
+ const MADA_GATEWAY_CODES = ['HP', 'CH'];
1212
+ const CREDIT_SHELL_SUFFIX = 'It can be used only for future booking on the airline website.';
1213
+ const GIFT_HINT = 'Please note: If you cancel either leg of your round-trip booking, the add-on/gift service will be cancelled automatically.';
1214
+ class SbdCancelDialogComponent {
1215
+ /**
1216
+ * The return leg is matched by an explicit 'Return' direction; the onward
1217
+ * leg is whatever's left (some bookings don't tag the outbound leg with an
1218
+ * explicit 'Departure' value), falling back to the first entry.
1219
+ */
1220
+ itineraryLeg(kind) {
1221
+ const legs = this.itineraryLegs();
1222
+ if (!legs.length)
1223
+ return null;
1224
+ const returnLeg = legs.find((l) => (l.direction ?? '').toLowerCase() === 'return') ?? null;
1225
+ if (kind === 'return')
1226
+ return returnLeg;
1227
+ return (legs.find((l) => (l.direction ?? '').toLowerCase() === 'departure') ??
1228
+ legs.find((l) => l !== returnLeg) ??
1229
+ legs[0] ??
1230
+ null);
1231
+ }
1232
+ fmtTime(dt) {
1233
+ if (!dt)
1234
+ return '—';
1235
+ try {
1236
+ return new Date(dt).toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });
1237
+ }
1238
+ catch {
1239
+ return dt;
1240
+ }
1241
+ }
1242
+ fmtShortDate(dt) {
1243
+ if (!dt)
1244
+ return '—';
1245
+ try {
1246
+ return new Date(dt).toLocaleDateString('en-GB', { weekday: 'short', day: '2-digit', month: 'short' });
1247
+ }
1248
+ catch {
1249
+ return dt;
1250
+ }
1251
+ }
1252
+ fmtMins(mins) {
1253
+ if (mins == null)
1254
+ return '';
1255
+ return `${Math.floor(mins / 60)}h ${mins % 60}m`;
1256
+ }
1257
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1258
+ applySourceOverride(base, breakdown) {
1259
+ const sel = this.selectedSource();
1260
+ if (!sel || !breakdown || !breakdown[sel])
1261
+ return this.reconcileSoleDestination(base);
1262
+ const src = breakdown[sel];
1263
+ const sourceRefundable = src.isRefundable !== false; // default true if not set
1264
+ if (!sourceRefundable) {
1265
+ // Source cannot process refund — zero out financial fields
1266
+ return this.reconcileSoleDestination({
1267
+ ...base,
1268
+ isRefundable: false,
1269
+ totalPenaltyPoints: 0,
1270
+ totalSupplierPenaltyPoints: 0,
1271
+ totalCancellationFee: 0,
1272
+ totalRefundAmount: 0,
1273
+ totalCardRefundAmount: 0,
1274
+ handlingFee: 0,
1275
+ });
1276
+ }
1277
+ // Prefer the source's own walletRefundAmount/totalCardRefundAmount when
1278
+ // the API provides them (they're already net of each other for this
1279
+ // source) — only fall back to deriving cardRefund when the API doesn't.
1280
+ const newTotal = Number(src.totalRefundAmount || 0);
1281
+ const walletRefund = src.walletRefundAmount !== undefined
1282
+ ? Number(src.walletRefundAmount || 0)
1283
+ : Number(base.walletRefundAmount || 0);
1284
+ const cardRefund = src.totalCardRefundAmount !== undefined
1285
+ ? Number(src.totalCardRefundAmount || 0)
1286
+ : Math.max(0, newTotal -
1287
+ Number(base.totalCreditRefundAmount || 0) -
1288
+ walletRefund -
1289
+ Number(base.totalLoyaltyPointsRefundAmount || 0));
1290
+ return this.reconcileSoleDestination({
1291
+ ...base,
1292
+ isRefundable: true,
1293
+ totalPenaltyPoints: Number(src.totalSupplierPenaltyPoints || 0),
1294
+ totalSupplierPenaltyPoints: Number(src.totalSupplierPenaltyPoints || 0),
1295
+ totalCancellationFee: Number(src.totalCancellationFee || 0),
1296
+ totalRefundAmount: newTotal,
1297
+ walletRefundAmount: walletRefund,
1298
+ totalCardRefundAmount: cardRefund,
1299
+ handlingFee: Number(src.handlingFee || 0),
1300
+ });
1301
+ }
1302
+ /**
1303
+ * When exactly one of the four refund-destination fields (wallet / card /
1304
+ * credit / loyalty) is non-zero, the backend has occasionally sent that
1305
+ * field as a pre-deduction value that disagrees with the declared total
1306
+ * (e.g. wallet = total + cancellation fee) — there's no ambiguity about
1307
+ * *where* the money goes, only a stale number in that one field, so trust
1308
+ * the declared total and correct the sole destination to match rather
1309
+ * than surfacing a false discrepancy.
1310
+ */
1311
+ reconcileSoleDestination(d) {
1312
+ if (!d?.isRefundable)
1313
+ return d;
1314
+ const wallet = Number(d.walletRefundAmount ?? 0);
1315
+ const card = Number(d.totalCardRefundAmount ?? 0);
1316
+ const credit = Number(d.totalCreditRefundAmount ?? 0);
1317
+ const loyalty = Number(d.totalLoyaltyPointsRefundAmount ?? 0);
1318
+ // A forfeited-points destination (e.g. Qitaf points funded separately
1319
+ // from the card refund) also counts as a real destination here — without
1320
+ // this, a legitimately split card+loyalty-points refund (e.g. 918 card +
1321
+ // 50 Qitaf points = 968 total) looks like a single stale "card" field to
1322
+ // this heuristic, which then force-overwrites the correct 918 with the
1323
+ // full 968 total and silently erases the split.
1324
+ const forfeitedLoyalty = this.forfeitedLoyaltyPointsFallback(d);
1325
+ const nonZero = [wallet, card, credit, loyalty, forfeitedLoyalty].filter((v) => v > 0);
1326
+ if (nonZero.length !== 1)
1327
+ return d;
1328
+ const total = Number(d.totalRefundAmountBeforeCeil ?? d.totalRefundAmount ?? 0);
1329
+ if (wallet > 0 && Math.abs(wallet - total) >= 0.01)
1330
+ return { ...d, walletRefundAmount: total };
1331
+ if (card > 0 && Math.abs(card - total) >= 0.01)
1332
+ return { ...d, totalCardRefundAmount: total };
1333
+ if (credit > 0 && Math.abs(credit - total) >= 0.01)
1334
+ return { ...d, totalCreditRefundAmount: total };
1335
+ if (loyalty > 0 && Math.abs(loyalty - total) >= 0.01)
1336
+ return { ...d, totalLoyaltyPointsRefundAmount: total };
1337
+ return d;
1338
+ }
1339
+ /**
1340
+ * Per-leg refund view (independent of the checkbox inclusion state) — used
1341
+ * to render both legs' own breakdown cards simultaneously.
1342
+ */
1343
+ legAdjustedData(legType) {
1344
+ const root = this.refundData();
1345
+ const leg = root?.legInfo?.[legType];
1346
+ if (!root || !leg)
1347
+ return null;
1348
+ const base = this.buildLegData(root, leg, legType);
1349
+ return this.applySourceOverride(base, leg.sourceBreakdown ?? null);
1350
+ }
1351
+ /** Deduction rows for a single leg's card. */
1352
+ legDeductions(legType) {
1353
+ return this.deductionRows(this.legAdjustedData(legType));
1354
+ }
1355
+ /**
1356
+ * Refundable pool ("Fare") for a leg — that leg's own paid amount
1357
+ * (`totalSupplierBookingAmount`). `totalRefundAmount + totalCancellationFee`
1358
+ * isn't reliable: `totalCancellationFee` doesn't include every deduction
1359
+ * (e.g. `handlingFee`), which produced a Fare that didn't reconcile with
1360
+ * Total Refund + the shown deductions.
1361
+ */
1362
+ legRefundPool(legType) {
1363
+ const d = this.legAdjustedData(legType);
1364
+ if (!d)
1365
+ return 0;
1366
+ return Number(d.totalSupplierBookingAmount || 0);
1367
+ }
1368
+ /** True once a leg's own departure time has passed. */
1369
+ legAlreadyFlown(legType) {
1370
+ const depTime = this.itineraryLeg(legType)?.depTime;
1371
+ if (!depTime)
1372
+ return false;
1373
+ const t = new Date(depTime).getTime();
1374
+ return !Number.isNaN(t) && t < Date.now();
1375
+ }
1376
+ /**
1377
+ * Toggle a leg's inclusion. A non-refundable leg can't be checked, and the
1378
+ * last checked leg can't be unchecked. Used on both the select-leg screen
1379
+ * and the round-trip leg cards.
1380
+ */
1381
+ toggleLeg(legType) {
1382
+ const refundable = legType === 'onward' ? this.onwardRefundable() : this.returnRefundable();
1383
+ if (!refundable)
1384
+ return;
1385
+ const self = legType === 'onward' ? this.onwardChecked : this.returnChecked;
1386
+ const other = legType === 'onward' ? this.returnChecked : this.onwardChecked;
1387
+ if (self() && !other())
1388
+ return;
1389
+ const isTurningOff = self();
1390
+ if (isTurningOff &&
1391
+ this.isRoundTrip() &&
1392
+ !this.isCrossSeller() &&
1393
+ !this.legAlreadyFlown(legType)) {
1394
+ // Airline doesn't allow cancelling this leg on its own — block the
1395
+ // uncheck, keep it checked+frozen, and surface the note.
1396
+ this.forcedLegType.set(legType);
1397
+ return;
1398
+ }
1399
+ this.forcedLegType.set(null);
1400
+ self.set(!self());
1401
+ // A leg may carry its own source list — re-resolve the selection.
1402
+ const breakdown = this.activeSourceBreakdown() ?? {};
1403
+ const sources = Object.keys(breakdown);
1404
+ if (sources.length && !breakdown[this.selectedSource()]) {
1405
+ this.selectedSource.set(sources[0]);
1406
+ }
1407
+ }
1408
+ /** Toggle a leg card's expanded/collapsed state. */
1409
+ toggleLegExpanded(legType) {
1410
+ const sig = legType === 'onward' ? this.onwardExpanded : this.returnExpanded;
1411
+ sig.set(!sig());
1412
+ }
1413
+ /**
1414
+ * Itemized refundable rows ("Fare" + named refundable add-ons/ancillaries)
1415
+ * from `refundBreakdown.refundable`. Empty when the backend hasn't sent a
1416
+ * breakdown yet — callers fall back to the single aggregate Fare row.
1417
+ */
1418
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1419
+ refundableBreakdownRows(d) {
1420
+ const b = d?.refundBreakdown?.refundable;
1421
+ if (!b)
1422
+ return [];
1423
+ // Fare always shows from refundBreakdown.refundable.fare, even when 0 —
1424
+ // never falls back to originalAmount.
1425
+ const rows = [{ label: 'Fare', value: Number(b.fare ?? 0) }];
1426
+ [...(b.addOns ?? []), ...(b.ancillaries ?? [])]
1427
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1428
+ .filter((item) => Number(item.amount) > 0)
1429
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1430
+ .forEach((item) => rows.push({ label: item.name, value: Number(item.amount) }));
1431
+ return rows;
1432
+ }
1433
+ /** Named refundable add-ons/ancillaries only (excludes Fare) — drives the "processed separately" note. */
1434
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1435
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1436
+ refundableAddonItems(d) {
1437
+ // Only `addOns` need manual Customer Service processing — `ancillaries`
1438
+ // (baggage, seat, auto check-in, …) are already itemized/refunded
1439
+ // automatically as part of the total, no separate note needed for them.
1440
+ const b = d?.refundBreakdown?.refundable;
1441
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1442
+ return (b?.addOns ?? []).filter((item) => Number(item.amount) > 0);
1443
+ }
1444
+ /** Whether to show the "add-on refund processed separately by Customer Service" note. */
1445
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1446
+ hasRefundableAddonNote(d) {
1447
+ return this.refundableAddonItems(d).length > 0;
1448
+ }
1449
+ /**
1450
+ * Deduction rows for the "Not Refunded" section. When an itemized
1451
+ * breakdown is present, its named add-ons/ancillaries replace the
1452
+ * aggregate `totalAncillaryRefundAmount`/`totalGiftAmount` rows (same
1453
+ * money, named instead of lumped); the other generic rows (cancellation
1454
+ * fee, handling fee, service charge) are unaffected.
1455
+ */
1456
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1457
+ deductionRows(d) {
1458
+ if (!d)
1459
+ return [];
1460
+ const b = d.refundBreakdown?.nonRefundable;
1461
+ if (!b) {
1462
+ return DEDUCTION_DEFS
1463
+ .filter(def => Number(d[def.key]) > 0)
1464
+ .map(def => ({ label: def.label, value: Number(d[def.key]), badge: def.badge }));
1465
+ }
1466
+ const itemRows = [...(b.addOns ?? []), ...(b.ancillaries ?? [])]
1467
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1468
+ .filter((item) => Number(item.amount) > 0)
1469
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1470
+ .map((item) => ({ label: item.name, value: Number(item.amount), badge: '' }));
1471
+ const aggregateRows = DEDUCTION_DEFS
1472
+ .filter(def => def.key !== 'totalAncillaryRefundAmount' && def.key !== 'totalGiftAmount')
1473
+ .filter(def => Number(d[def.key]) > 0)
1474
+ .map(def => ({ label: def.label, value: Number(d[def.key]), badge: def.badge }));
1475
+ return [...itemRows, ...aggregateRows];
1476
+ }
1477
+ // Resolve the PNR for the currently selected leg from the booking's pnr_info list.
1478
+ resolvePnr() {
1479
+ const list = this.pnrInfo() ?? [];
1480
+ if (!list.length)
1481
+ return '';
1482
+ const sel = this.selectedLeg();
1483
+ const byDir = (dir) => list.find(p => p.LegDirectionality === dir)?.pnr;
1484
+ if (sel === 'return')
1485
+ return byDir('Return') ?? list[0]?.pnr ?? '';
1486
+ if (sel === 'onward')
1487
+ return byDir('Departure') ?? list[0]?.pnr ?? '';
1488
+ return byDir('Departure') ?? list[0]?.pnr ?? '';
1489
+ }
1490
+ /** Rows that land in the wallet — plain wallet, wallet+loyalty combined, or loyalty-only — share the wallet icon/layout on the result screen. */
1491
+ isWalletDestinationRow(r) {
1492
+ return r.key === 'walletRefundAmount' || r.key === 'totalLoyaltyPoints';
1493
+ }
1494
+ fmtResult(value) {
1495
+ const cur = this.cancelResult()?.currency ?? this.refundData()?.currency ?? 'SAR';
1496
+ return `${Number(value || 0).toLocaleString('en-SA', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} ${cur}`;
1497
+ }
1498
+ /**
1499
+ * `totalLoyaltyPoints` is normally the forfeited-at-booking points count
1500
+ * (no refund destination of its own). But when the backend never
1501
+ * populates `totalLoyaltyPointsRefundAmount` for a `loyaltyPaymentType`
1502
+ * booking, this is the only signal that a loyalty program was involved,
1503
+ * so it doubles as a split-refund row (alongside Card/Wallet) rather than
1504
+ * disappearing from the refund breakdown entirely. Always shown as its
1505
+ * own row under the program name (e.g. "Wala") — never merged into the
1506
+ * generic Wallet row, even when `walletRefundAmount` is also non-zero.
1507
+ */
1508
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1509
+ forfeitedLoyaltyPointsFallback(d) {
1510
+ if (!d?.loyaltyPaymentType)
1511
+ return 0;
1512
+ if (Number(d.totalLoyaltyPointsRefundAmount ?? 0) > 0)
1513
+ return 0;
1514
+ return Number(d.totalLoyaltyPoints ?? 0);
1515
+ }
1516
+ /**
1517
+ * Some `/confirm` responses for a single-leg (non-cross-seller) booking
1518
+ * put `walletRefundAmount`/`totalLoyaltyPoints` on the wrong side at the
1519
+ * root — e.g. root reads `walletRefundAmount: 40, totalLoyaltyPoints: 0`
1520
+ * while the single leg (which should mirror root exactly here) reads
1521
+ * `walletRefundAmount: 0, totalLoyaltyPoints: 40`. Detected narrowly: root
1522
+ * shows no forfeited loyalty despite `loyaltyPaymentType` being set, the
1523
+ * root wallet amount exactly equals the leg's loyalty points, and the
1524
+ * leg's own wallet amount disagrees — in that specific case the leg's
1525
+ * values are trusted instead, since with only one leg they're unambiguous.
1526
+ *
1527
+ * A second, more common shape: root doesn't swap at all, it folds the
1528
+ * points straight into `walletRefundAmount` as one opaque lump sum (e.g.
1529
+ * root wallet 340 = the leg's own cash wallet 300 + the leg's forfeited
1530
+ * loyalty points 40). Split that back into the two parts the leg already
1531
+ * tells us about too, so the result screen can show a combined wallet card
1532
+ * with the program named instead of one unexplained total.
1533
+ */
1534
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1535
+ correctedConfirmWalletAndLoyalty(d) {
1536
+ const rootWallet = Number(d.walletRefundAmount ?? 0);
1537
+ const forfeitedFromRoot = this.forfeitedLoyaltyPointsFallback(d);
1538
+ if (forfeitedFromRoot > 0 || !d.loyaltyPaymentType) {
1539
+ return { wallet: rootWallet, forfeitedLoyalty: forfeitedFromRoot };
1540
+ }
1541
+ const onward = d.legInfo?.onward;
1542
+ const ret = d.legInfo?.return;
1543
+ const leg = onward && !ret ? onward : ret && !onward ? ret : null;
1544
+ if (!leg)
1545
+ return { wallet: rootWallet, forfeitedLoyalty: 0 };
1546
+ const legLoyalty = Number(leg.totalLoyaltyPoints ?? 0);
1547
+ const legWallet = Number(leg.walletRefundAmount ?? 0);
1548
+ // Full swap: root reads the leg's loyalty amount where its wallet should be.
1549
+ if (legLoyalty > 0 &&
1550
+ rootWallet === legLoyalty &&
1551
+ legWallet !== rootWallet &&
1552
+ Number(d.totalLoyaltyPointsRefundAmount ?? 0) === 0) {
1553
+ return { wallet: legWallet, forfeitedLoyalty: legLoyalty };
1554
+ }
1555
+ // Lump sum: root wallet = leg's cash wallet + leg's forfeited loyalty.
1556
+ if (legLoyalty > 0 &&
1557
+ legWallet > 0 &&
1558
+ Math.abs(rootWallet - (legWallet + legLoyalty)) < 1 &&
1559
+ Number(d.totalLoyaltyPointsRefundAmount ?? 0) === 0) {
1560
+ return { wallet: legWallet, forfeitedLoyalty: legLoyalty };
1561
+ }
1562
+ // Newer `/confirm` responses drop the loyalty portion from both root and
1563
+ // leg totals — `walletRefundAmount` arrives as one opaque lump that's the
1564
+ // cash-wallet portion (`totalWalletAmount`) plus a loyalty-program amount.
1565
+ // When `loyaltyPaymentType` is set, the lump exceeds the cash-wallet
1566
+ // portion, and it's a genuine card+wallet split (not a "send everything to
1567
+ // the wallet" refund), the difference is that loyalty amount.
1568
+ const cashWallet = Number(d.totalWalletAmount ?? 0);
1569
+ const impliedLoyalty = rootWallet - cashWallet;
1570
+ if (impliedLoyalty > 0.01 &&
1571
+ cashWallet > 0 &&
1572
+ Number(d.totalCardRefundAmount ?? 0) > 0 &&
1573
+ Number(d.totalLoyaltyPointsRefundAmount ?? 0) === 0) {
1574
+ return { wallet: cashWallet, forfeitedLoyalty: impliedLoyalty };
1575
+ }
1576
+ // The whole wallet refund is the loyalty-program balance — no cash-wallet
1577
+ // portion (`totalWalletAmount` 0) and no card portion, but
1578
+ // `loyaltyPaymentType` is set. Attribute all of it to the program so the
1579
+ // popup names it (e.g. "Wala") with the permanent-points note.
1580
+ if (cashWallet === 0 &&
1581
+ rootWallet > 0 &&
1582
+ Number(d.totalCardRefundAmount ?? 0) === 0 &&
1583
+ Number(d.totalLoyaltyPointsRefundAmount ?? 0) === 0) {
1584
+ return { wallet: 0, forfeitedLoyalty: rootWallet };
1585
+ }
1586
+ return { wallet: rootWallet, forfeitedLoyalty: 0 };
1587
+ }
1588
+ togglePaidSummary() {
1589
+ this.paidSummaryExpanded.set(!this.paidSummaryExpanded());
1590
+ }
1591
+ /**
1592
+ * The backend has sent `paymentMethodCustomerUsed` as both a short code
1593
+ * (`"CH"`) and a full gateway name (`"Card"`) at different times — match
1594
+ * either, case-insensitively, against the known codes/labels rather than
1595
+ * assuming one format.
1596
+ */
1597
+ normalizeGatewayCode(raw) {
1598
+ if (!raw)
1599
+ return '';
1600
+ const needle = raw.trim().toLowerCase();
1601
+ const codes = Object.keys(PAYMENT_GATEWAY_LABELS);
1602
+ const byCode = codes.find((code) => code.toLowerCase() === needle);
1603
+ if (byCode)
1604
+ return byCode;
1605
+ return codes.find((code) => PAYMENT_GATEWAY_LABELS[code].toLowerCase() === needle) ?? '';
1606
+ }
1607
+ /**
1608
+ * Wallet-row label for a loyalty-program redemption — `loyaltyPaymentType`
1609
+ * (e.g. Wala, Qitaf) names the program that redeemed into the wallet
1610
+ * balance and applies regardless of `paymentMethodCustomerUsed`; the
1611
+ * backend can report an unrelated original payment gateway (e.g. Tabby)
1612
+ * alongside a wallet portion that's actually a loyalty redemption.
1613
+ */
1614
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1615
+ walletOrLoyaltyLabel(d) {
1616
+ const loyalty = d?.loyaltyPaymentType ?? '';
1617
+ return PAYMENT_GATEWAY_LABELS[loyalty] ?? '';
1618
+ }
1619
+ constructor() {
1620
+ this.bookingService = inject(SbdBookingService);
1621
+ this.toast = inject(SbdToastService);
1622
+ this.visible = input(false);
1623
+ this.bookingId = input('');
1624
+ this.product = input('flight');
1625
+ this.pnrInfo = input([]);
1626
+ /** Same `booking_info` payload the dashboard already fetches (getBooking(..., 'booking_info')) — reused here to render real route/time details on the select-leg cards, instead of a second API call. */
1627
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1628
+ this.bookingInfo = input(null);
1629
+ this.closed = output();
1630
+ this.confirmed = output();
1631
+ /** Per-leg route/time details, mirroring app-sbd-flight-itinerary-card's mapping of booking_info.leg_info. */
1632
+ this.itineraryLegs = computed(() => {
1633
+ const bi = this.bookingInfo()?.data?.booking_info;
1634
+ if (!bi)
1635
+ return [];
1636
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1637
+ return (bi.leg_info ?? []).map((leg, i) => ({
1638
+ direction: leg.LegDirectionality,
1639
+ airlineName: (leg.LegAirlinesNames ?? []).join(', '),
1640
+ cabin: leg.LegCabins?.[0]?.Name ?? '',
1641
+ from: leg.LegDepartureAirportCode,
1642
+ to: leg.LegArrivalAirportCode,
1643
+ depTime: leg.LegDepartureDateTime,
1644
+ arrTime: leg.LegArrivalDateTime,
1645
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1646
+ stops: (leg.Stops ?? []),
1647
+ totalMinutes: bi.duration?.legs?.[i]?.Segments
1648
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1649
+ ?.reduce((sum, s) => sum + (s.SegmentElapsedTimeMinutes ?? 0), 0) ?? null,
1650
+ operatedBy: leg.supplierName ? [leg.supplierName] : [],
1651
+ }));
1652
+ });
1653
+ this.refundData = signal(null);
1654
+ this.cancelResult = signal(null);
1655
+ this.loading = signal(false);
1656
+ this.cancelling = signal(false);
1657
+ this.confirming = signal(false);
1658
+ /**
1659
+ * 'select-leg' shown once the refund preview resolves, for round-trip
1660
+ * bookings the backend hasn't already restricted to a single leg — mirrors
1661
+ * almatar-b2c's select-leg screen. Skipped straight to 'details' otherwise.
1662
+ */
1663
+ this.step = signal('loading');
1664
+ /** Per-leg inclusion (checkbox) and expand/collapse state — round-trip only. */
1665
+ this.onwardChecked = signal(true);
1666
+ this.returnChecked = signal(true);
1667
+ this.onwardExpanded = signal(true);
1668
+ this.returnExpanded = signal(true);
1669
+ this.selectedSource = signal('');
1670
+ this.cancelHint = signal('');
1671
+ this.paidSummaryExpanded = signal(false);
1672
+ this.preferredRefundMethod = signal('originalPaymentMethod');
1673
+ this.CANCEL_HINTS = [
1674
+ 'Loading your data...',
1675
+ 'Fetching Sabre data...',
1676
+ 'Fetching ATC data...',
1677
+ 'Calculating refund amount...',
1678
+ 'We are almost done, please wait...',
1679
+ ];
1680
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1681
+ this.hintTimer = null;
1682
+ this.hasOnward = computed(() => !!this.refundData()?.legInfo?.onward);
1683
+ this.hasReturn = computed(() => !!this.refundData()?.legInfo?.return);
1684
+ this.isRoundTrip = computed(() => this.hasOnward() && this.hasReturn());
1685
+ /** Which leg(s) the backend allows cancelling, from the refund preview response. */
1686
+ this.legDirectionality = computed(() => this.refundData()?.legDirectionality);
1687
+ /**
1688
+ * Whether to render the round-trip "two leg cards" UI. Only when the
1689
+ * backend explicitly allows cancelling both legs — otherwise (ONWARD-only /
1690
+ * RETURN-only) show a single card for that leg, same as a one-way booking.
1691
+ */
1692
+ this.showRoundTripLegCards = computed(() => this.isRoundTrip() && (this.legDirectionality() ?? 'BOTH') === 'BOTH');
1693
+ /** Derived from the per-leg checkboxes — 'both' unless exactly one is unchecked. */
1694
+ this.selectedLeg = computed(() => {
1695
+ if (!this.isRoundTrip())
1696
+ return 'both';
1697
+ const dir = this.legDirectionality();
1698
+ if (dir === 'ONWARD')
1699
+ return 'onward';
1700
+ if (dir === 'RETURN')
1701
+ return 'return';
1702
+ const onward = this.onwardChecked();
1703
+ const ret = this.returnChecked();
1704
+ if (onward && ret)
1705
+ return 'both';
1706
+ return onward ? 'onward' : 'return';
1707
+ });
1708
+ // Returns the sourceBreakdown for the currently selected leg (or root for "both")
1709
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1710
+ this.activeSourceBreakdown = computed(() => {
1711
+ const sel = this.selectedLeg();
1712
+ const root = this.refundData();
1713
+ if (!root)
1714
+ return null;
1715
+ // A leg-scoped view (single leg, or both legs summed) must not fall back
1716
+ // to root.sourceBreakdown — its totals are for the whole/original
1717
+ // itinerary, not scoped to the leg(s) actually shown, and applying it
1718
+ // would clobber the correctly leg-scoped totals computed in
1719
+ // `currentData()`.
1720
+ if (sel === 'onward')
1721
+ return root.legInfo?.onward?.sourceBreakdown ?? null;
1722
+ if (sel === 'return')
1723
+ return root.legInfo?.return?.sourceBreakdown ?? null;
1724
+ if (root.legInfo?.onward && root.legInfo?.return)
1725
+ return null;
1726
+ return root.sourceBreakdown ?? null;
1727
+ });
1728
+ this.availableSources = computed(() => Object.keys(this.activeSourceBreakdown() ?? {}));
1729
+ this.currentData = computed(() => {
1730
+ const root = this.refundData();
1731
+ if (!root)
1732
+ return null;
1733
+ const sel = this.selectedLeg();
1734
+ if (sel === 'onward' && root.legInfo?.onward)
1735
+ return this.buildLegData(root, root.legInfo.onward, 'onward');
1736
+ if (sel === 'return' && root.legInfo?.return)
1737
+ return this.buildLegData(root, root.legInfo.return, 'return');
1738
+ // Payment/refund figures come from `legInfo`, never root (root isn't
1739
+ // reliably scoped to whichever leg(s) ended up selected) — combine both
1740
+ // legs when both are in play, otherwise use whichever one exists.
1741
+ const onward = root.legInfo?.onward;
1742
+ const ret = root.legInfo?.return;
1743
+ if (onward && ret)
1744
+ return this.buildCombinedLegData(root, onward, ret);
1745
+ if (onward && !ret)
1746
+ return this.buildLegData(root, onward, 'onward');
1747
+ if (ret && !onward)
1748
+ return this.buildLegData(root, ret, 'return');
1749
+ return root;
1750
+ });
1751
+ // currentData with source-specific penalty/refund/refundability overrides applied
1752
+ this.adjustedData = computed(() => {
1753
+ const base = this.currentData();
1754
+ if (!base)
1755
+ return null;
1756
+ return this.applySourceOverride(base, this.activeSourceBreakdown());
1757
+ });
1758
+ /**
1759
+ * Non-CrossSeller airlines require the whole round-trip/multicity
1760
+ * itinerary to be cancelled together — a leg can't be excluded on its own
1761
+ * unless it has already flown (which naturally degrades to a one-way
1762
+ * cancellation of just the remaining leg). Set when an uncheck attempt was
1763
+ * blocked for this reason, so the leg's checkbox can be frozen (locked)
1764
+ * and the note shown.
1765
+ */
1766
+ this.forcedLegType = signal(null);
1767
+ this.sourceNonRefundableHint = computed(() => {
1768
+ const sel = this.selectedSource();
1769
+ if (!sel)
1770
+ return '';
1771
+ const src = (this.activeSourceBreakdown() ?? {})[sel];
1772
+ if (!src || src.isRefundable !== false)
1773
+ return '';
1774
+ return `Cancellation via ${sel} is not eligible for a refund. Please select a different source.`;
1775
+ });
1776
+ this.deductions = computed(() => this.deductionRows(this.adjustedData()));
1777
+ this.refunds = computed(() => {
1778
+ const d = this.adjustedData();
1779
+ if (!d)
1780
+ return [];
1781
+ const isLoyalty = this.isLoyaltyMethod();
1782
+ const paymentLabel = this.customerPaymentLabel();
1783
+ const rows = REFUND_DEFS
1784
+ .filter(def => Number(d[def.key]) > 0)
1785
+ .map(def => ({
1786
+ // Wallet row always reads "Almatar Wallet" (def.label) — the loyalty
1787
+ // program that funded it gets its own dedicated row below via
1788
+ // `forfeitedLoyaltyPointsFallback`, so the two stay visually
1789
+ // distinct instead of merging into one label.
1790
+ // Card row: card/loyalty stay distinct — don't relabel the card
1791
+ // portion with the loyalty program name too, and never surface an
1792
+ // internal processor name (empty `paymentLabel` falls back to the
1793
+ // generic def.label, e.g. "Refund Amount").
1794
+ label: def.key === 'totalCardRefundAmount' && !isLoyalty && paymentLabel
1795
+ ? `${def.label} ${paymentLabel}`
1796
+ : def.label,
1797
+ value: Number(d[def.key]),
1798
+ }));
1799
+ const forfeitedLoyalty = this.forfeitedLoyaltyPointsFallback(d);
1800
+ if (forfeitedLoyalty > 0) {
1801
+ rows.push({ label: this.walletOrLoyaltyLabel(d), value: forfeitedLoyalty });
1802
+ }
1803
+ return rows;
1804
+ });
1805
+ this.creditShellNote = computed(() => {
1806
+ // Golden Guarantee overrides credit-shell messaging entirely — the
1807
+ // dedicated success notice (`goldenGuaranteeNote`) is shown instead.
1808
+ if (this.hasGoldenGuaranteeGift())
1809
+ return '';
1810
+ const d = this.currentData();
1811
+ if (!d)
1812
+ return '';
1813
+ const legInfo = d.legInfo ?? {};
1814
+ const hasO = !!legInfo.onward;
1815
+ const hasR = !!legInfo.return;
1816
+ const oShell = legInfo.onward?.isCreditShell === true;
1817
+ const rShell = legInfo.return?.isCreditShell === true;
1818
+ const isShell = (hasO && hasR && oShell && rShell) ||
1819
+ (hasO && !hasR && oShell) ||
1820
+ (!hasO && hasR && rShell) ||
1821
+ (hasO && hasR && (oShell || rShell));
1822
+ if (!isShell)
1823
+ return '';
1824
+ const amount = this.fmt(Number(this.adjustedData()?.totalRefundAmount ?? d.totalRefundAmount ?? 0));
1825
+ const pnr = this.resolvePnr();
1826
+ const pnrText = pnr ? `the same PNR (${pnr})` : 'the same PNR';
1827
+ return `The refund of ${amount} will be issued as airline credit under ${pnrText}. ${CREDIT_SHELL_SUFFIX}`;
1828
+ });
1829
+ /**
1830
+ * Booking includes a Golden Guarantee gift ("Cancel for any reason") —
1831
+ * overrides all credit-shell handling below, since a Golden Guarantee
1832
+ * cancellation always refunds 100% through the standard payment method
1833
+ * rather than the airline credit shell. May be carried on either the
1834
+ * preview (`refundData`) or the confirm (`cancelResult`) response — both
1835
+ * share the same shape, and either one may set it independently of the
1836
+ * other.
1837
+ */
1838
+ this.hasGoldenGuaranteeGift = computed(() => this.refundData()?.hasGoldenGuaranteeGift === true ||
1839
+ this.cancelResult()?.hasGoldenGuaranteeGift === true);
1840
+ // Whether the refund is a credit-shell refund (whole booking or either leg).
1841
+ // Such refunds can only return to the airline credit shell, so the
1842
+ // refund-method picker is replaced with a single read-only credit notice.
1843
+ // A Golden Guarantee gift overrides this to `false` — that portion needs an
1844
+ // actual destination choice, so the booking falls back to the standard
1845
+ // refund-method flow despite being flagged credit-shell.
1846
+ this.isCreditShell = computed(() => {
1847
+ if (this.hasGoldenGuaranteeGift())
1848
+ return false;
1849
+ const d = this.refundData();
1850
+ return (d?.isCreditShell === true ||
1851
+ d?.legInfo?.onward?.isCreditShell === true ||
1852
+ d?.legInfo?.return?.isCreditShell === true);
1853
+ });
1854
+ /**
1855
+ * Refundable ancillaries (baggage, seats, …) that carry their own
1856
+ * `isCreditShell` flag, independent of the booking-level flag — rendered
1857
+ * as individual cards on the credit-shell refund-method screen so each
1858
+ * one can show either the credit-shell card style or the normal wallet
1859
+ * card style (both include the same nested credit-shell note when true).
1860
+ */
1861
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1862
+ this.creditShellAncillaryItems = computed(() => (this.adjustedData()?.refundBreakdown?.refundable?.ancillaries ?? [])
1863
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1864
+ .filter((item) => Number(item.amount) > 0));
1865
+ // Airline that issues the credit shell, for the "<supplier> website credit" card.
1866
+ this.creditShellSupplierName = computed(() => {
1867
+ const d = this.refundData();
1868
+ return (d?.legInfo?.onward?.supplierName ??
1869
+ d?.legInfo?.return?.supplierName ??
1870
+ d?.supplierName ??
1871
+ d?.appliedSource ??
1872
+ '');
1873
+ });
1874
+ // Header title reflecting the refund type for the active selection.
1875
+ this.refundTypeLabel = computed(() => {
1876
+ if (!this.refundData()?.isRefundable)
1877
+ return 'Refund Details';
1878
+ return this.creditShellNote() ? 'Credit Shell Refund' : 'Refund Details';
1879
+ });
1880
+ this.currency = computed(() => this.refundData()?.currency ?? 'SAR');
1881
+ // Self-consistency check between the paid amount, deductions and the system refund.
1882
+ // Surfaced to the agent so mismatched/over-deducted refunds can be caught before confirming.
1883
+ this.discrepancyNote = computed(() => {
1884
+ const d = this.adjustedData();
1885
+ if (!d || !d.isRefundable)
1886
+ return '';
1887
+ const paid = Number(d.originalAmount || 0);
1888
+ const deduction = Number(d.totalCancellationFee || 0);
1889
+ const refund = Number(d.totalRefundAmount || 0);
1890
+ const issues = [];
1891
+ if (deduction > paid + 0.01)
1892
+ issues.push(`Total deduction (${this.fmt(deduction)}) exceeds the paid amount (${this.fmt(paid)}).`);
1893
+ if (Math.abs((paid - deduction) - refund) > 0.01)
1894
+ issues.push(`Computed refund (${this.fmt(paid - deduction)}) doesn't match the system refund (${this.fmt(refund)}).`);
1895
+ return issues.join(' ');
1896
+ });
1897
+ /** Real per-booking CrossSeller flag from the refund preview — CrossSeller suppliers allow cancelling either leg independently. */
1898
+ this.isCrossSeller = computed(() => this.refundData()?.isCrossSeller === true);
1899
+ // Non-CrossSeller round trips are one itinerary sold as a single fare —
1900
+ // both legs share the same refundability, sourced from root, even when
1901
+ // the backend's legInfo entry for a leg is missing or inconsistent.
1902
+ // CrossSeller legs are independently sellable, so each leg's own field
1903
+ // still governs there.
1904
+ this.onwardRefundable = computed(() => {
1905
+ if (!this.hasOnward())
1906
+ return false;
1907
+ return this.isCrossSeller()
1908
+ ? this.refundData()?.legInfo?.onward?.isRefundable === true
1909
+ : this.refundData()?.isRefundable === true;
1910
+ });
1911
+ this.returnRefundable = computed(() => {
1912
+ if (!this.hasReturn())
1913
+ return false;
1914
+ return this.isCrossSeller()
1915
+ ? this.refundData()?.legInfo?.return?.isRefundable === true
1916
+ : this.refundData()?.isRefundable === true;
1917
+ });
1918
+ this.isMixed = computed(() => this.isRoundTrip() && this.onwardRefundable() !== this.returnRefundable());
1919
+ /** Both legs present and BOTH non-refundable — nothing can be refunded for this booking. */
1920
+ this.allLegsNonRefundable = computed(() => this.isRoundTrip() && !this.onwardRefundable() && !this.returnRefundable());
1921
+ this.mixedHint = computed(() => {
1922
+ // Only relevant when agent is about to cancel both legs together —
1923
+ // individual leg tabs already show their own non-refundable notice.
1924
+ if (!this.isMixed() || this.selectedLeg() !== 'both')
1925
+ return '';
1926
+ if (!this.onwardRefundable())
1927
+ return '⚠️ Onward leg is not eligible for refund. Please contact customer service if you want to cancel it.';
1928
+ if (!this.returnRefundable())
1929
+ return '⚠️ Return leg is not eligible for refund. Please contact customer service if you want to cancel it.';
1930
+ return '';
1931
+ });
1932
+ this.giftHint = computed(() => {
1933
+ const root = this.refundData();
1934
+ if (!root || !this.isRoundTrip())
1935
+ return '';
1936
+ const hasGift = Number(root.totalGiftAmount || 0) > 0 ||
1937
+ Number(root.legInfo?.onward?.totalGiftAmount || 0) > 0 ||
1938
+ Number(root.legInfo?.return?.totalGiftAmount || 0) > 0;
1939
+ return hasGift ? GIFT_HINT : '';
1940
+ });
1941
+ this.selectionHint = computed(() => {
1942
+ if (!this.isRoundTrip())
1943
+ return '';
1944
+ const map = {
1945
+ onward: 'You are about to cancel: Onward leg only',
1946
+ return: 'You are about to cancel: Return leg only',
1947
+ both: 'You are about to cancel: Both legs',
1948
+ };
1949
+ return map[this.selectedLeg()];
1950
+ });
1951
+ this.confirmDeductions = computed(() => {
1952
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1953
+ const d = this.cancelResult();
1954
+ if (!d)
1955
+ return [];
1956
+ return DEDUCTION_DEFS.filter(def => Number(d[def.key]) > 0).map(def => ({ label: def.label, value: Number(d[def.key]) }));
1957
+ });
1958
+ this.confirmRefunds = computed(() => {
1959
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1960
+ const d = this.cancelResult();
1961
+ if (!d)
1962
+ return [];
1963
+ const code = this.normalizeGatewayCode(d.paymentMethodCustomerUsed);
1964
+ const isLoyalty = LOYALTY_GATEWAY_CODES.includes(code);
1965
+ const isMada = this.confirmIsMadaMethod();
1966
+ const paymentLabel = code && !INTERNAL_GATEWAY_CODES.includes(code) ? PAYMENT_GATEWAY_LABELS[code] : '';
1967
+ const rows = [];
1968
+ const { wallet, forfeitedLoyalty } = this.correctedConfirmWalletAndLoyalty(d);
1969
+ // Golden Guarantee overrides credit-shell handling on the result screen
1970
+ // too — whatever landed as a credit-shell amount folds into the normal
1971
+ // original-payment-method row instead of getting its own "airline
1972
+ // credit" card, so the confirmation reads like an ordinary Sabre/Amadeus
1973
+ // refund.
1974
+ const goldenGuaranteeOverride = this.hasGoldenGuaranteeGift();
1975
+ // Credit-shell row goes first — matches the design (airline credit above
1976
+ // the wallet row) — before the card and wallet rows below.
1977
+ if (!goldenGuaranteeOverride) {
1978
+ const credit = Number(d.totalCreditRefundAmount ?? 0);
1979
+ if (credit > 0) {
1980
+ const supplier = d.appliedSource || this.creditShellSupplierName();
1981
+ rows.push({
1982
+ key: 'totalCreditRefundAmount',
1983
+ label: supplier ? `${supplier} website credit` : 'Credit Shell Refund',
1984
+ value: credit,
1985
+ });
1986
+ }
1987
+ }
1988
+ const cardAmount = Number(d.totalCardRefundAmount ?? 0) +
1989
+ (goldenGuaranteeOverride ? Number(d.totalCreditRefundAmount ?? 0) : 0);
1990
+ if (cardAmount > 0) {
1991
+ rows.push({
1992
+ key: 'totalCardRefundAmount',
1993
+ label: isMada
1994
+ ? 'Mada Card'
1995
+ : !isLoyalty && paymentLabel
1996
+ ? `Refund Amount ${paymentLabel}`
1997
+ : 'Refund Amount',
1998
+ value: cardAmount,
1999
+ });
2000
+ }
2001
+ if (wallet > 0 && forfeitedLoyalty > 0) {
2002
+ // Wallet cash + a loyalty program's points both landed in the wallet —
2003
+ // shown as one combined card with the two sources broken out as named
2004
+ // sub-rows, rather than two separate top-level cards.
2005
+ rows.push({
2006
+ key: 'walletRefundAmount',
2007
+ label: 'Almatar Wallet',
2008
+ value: wallet + forfeitedLoyalty,
2009
+ subRows: [
2010
+ { label: 'Almatar wallet points', value: wallet },
2011
+ { label: `${this.walletOrLoyaltyLabel(d)} points`, value: forfeitedLoyalty },
2012
+ ],
2013
+ hasLoyaltyNote: true,
2014
+ });
2015
+ }
2016
+ else if (wallet > 0) {
2017
+ rows.push({ key: 'walletRefundAmount', label: 'Almatar Wallet', value: wallet });
2018
+ }
2019
+ else if (forfeitedLoyalty > 0) {
2020
+ // Loyalty-only — no wallet cash involved, still lands as permanent
2021
+ // wallet points, so it keeps the wallet-style card + note.
2022
+ rows.push({
2023
+ key: 'totalLoyaltyPoints',
2024
+ label: this.walletOrLoyaltyLabel(d),
2025
+ value: forfeitedLoyalty,
2026
+ hasLoyaltyNote: true,
2027
+ });
2028
+ }
2029
+ REFUND_DEFS
2030
+ .filter((def) => def.key !== 'walletRefundAmount' &&
2031
+ def.key !== 'totalCardRefundAmount' &&
2032
+ def.key !== 'totalCreditRefundAmount' &&
2033
+ Number(d[def.key]) > 0)
2034
+ .forEach((def) => {
2035
+ rows.push({ key: def.key, label: def.label, value: Number(d[def.key]) });
2036
+ });
2037
+ return rows;
2038
+ });
2039
+ /**
2040
+ * The result screen's "issued as an Airline Credit Shell" note. Golden
2041
+ * Guarantee overrides this to '' — the confirmation renders like a
2042
+ * standard Sabre/Amadeus refund instead (see `confirmRefunds`' folding
2043
+ * of any credit-shell amount into the normal payment-method row).
2044
+ */
2045
+ this.confirmCreditShellNote = computed(() => {
2046
+ if (this.hasGoldenGuaranteeGift())
2047
+ return '';
2048
+ const d = this.cancelResult();
2049
+ if (!d)
2050
+ return '';
2051
+ const legInfo = d.legInfo ?? {};
2052
+ const hasO = !!legInfo.onward;
2053
+ const hasR = !!legInfo.return;
2054
+ const oShell = legInfo.onward?.isCreditShell === true;
2055
+ const rShell = legInfo.return?.isCreditShell === true;
2056
+ const topShell = d.isCreditShell === true;
2057
+ if ((hasO && hasR && oShell && rShell) || (hasO && !hasR && oShell) || (!hasO && hasR && rShell) || topShell)
2058
+ return 'The refund has been issued as an Airline Credit Shell and can be used for future bookings on the airline website.';
2059
+ if (hasO && hasR) {
2060
+ if (oShell)
2061
+ return 'The refund for the onward flight has been issued as an Airline Credit Shell.';
2062
+ if (rShell)
2063
+ return 'The refund for the return flight has been issued as an Airline Credit Shell.';
2064
+ }
2065
+ return '';
2066
+ });
2067
+ /**
2068
+ * The customer-facing total (ceiled) must match the pre-rounding total the
2069
+ * refund is actually computed from — i.e. `totalRefundAmount` must be
2070
+ * `totalRefundAmountBeforeCeil` rounded up to a whole currency unit (e.g.
2071
+ * 1454.5 → 1455). A mismatch here means the supplier response itself is
2072
+ * internally inconsistent and must block confirmation.
2073
+ */
2074
+ this.refundReconciles = computed(() => {
2075
+ const d = this.adjustedData();
2076
+ if (!d || !d.isRefundable)
2077
+ return true;
2078
+ const beforeCeil = Number(d.totalRefundAmountBeforeCeil ?? d.totalRefundAmount ?? 0);
2079
+ const total = Number(d.totalRefundAmount ?? beforeCeil);
2080
+ return Math.abs(Math.ceil(beforeCeil) - total) < 1;
2081
+ });
2082
+ this.canConfirm = computed(() => {
2083
+ const d = this.adjustedData();
2084
+ return !!d?.isRefundable && this.refundReconciles() && !this.cancelling();
2085
+ });
2086
+ /** How many of {wallet, card, loyalty} the refund is actually split across. */
2087
+ this.splitPartsCount = computed(() => {
2088
+ const d = this.adjustedData();
2089
+ if (!d)
2090
+ return 0;
2091
+ return [
2092
+ d.walletRefundAmount,
2093
+ d.totalCardRefundAmount,
2094
+ d.totalLoyaltyPointsRefundAmount,
2095
+ this.forfeitedLoyaltyPointsFallback(d),
2096
+ ].filter((v) => Number(v ?? 0) > 0).length;
2097
+ });
2098
+ /**
2099
+ * A portion of the refund is forced to the wallet (e.g. loyalty points used)
2100
+ * — in that case offer a "Split Refund" option instead of a pure
2101
+ * original-payment-method option, since the whole amount can't go to card.
2102
+ */
2103
+ this.hasSplitRefund = computed(() => Number(this.adjustedData()?.totalRefundAmount ?? 0) > 0 && this.splitPartsCount() >= 2);
2104
+ /**
2105
+ * Wallet covers the whole refund (no card or loyalty portion at all) — show
2106
+ * the wallet option alone rather than a "split" with empty other sides.
2107
+ */
2108
+ this.isWalletOnlyRefund = computed(() => {
2109
+ const d = this.adjustedData();
2110
+ return (Number(d?.walletRefundAmount ?? 0) > 0 &&
2111
+ Number(d?.totalCardRefundAmount ?? 0) === 0 &&
2112
+ Number(d?.totalLoyaltyPointsRefundAmount ?? 0) === 0 &&
2113
+ this.forfeitedLoyaltyPointsFallback(d) === 0);
2114
+ });
2115
+ /**
2116
+ * The entire refund is forfeited loyalty points (e.g. Wala) landing back as
2117
+ * wallet balance — no cash-wallet, card, credit or real
2118
+ * loyalty-points-refund portion at all. Without this the picker falls
2119
+ * through to the "Original Payment Method" card and mislabels the
2120
+ * destination as the original gateway (e.g. "Tabby"), even though
2121
+ * `totalCardRefundAmount` is 0 and nothing goes back to that card.
2122
+ */
2123
+ this.isLoyaltyOnlyRefund = computed(() => {
2124
+ const d = this.adjustedData();
2125
+ return (this.forfeitedLoyaltyPointsFallback(d) > 0 &&
2126
+ Number(d?.walletRefundAmount ?? 0) === 0 &&
2127
+ Number(d?.totalCardRefundAmount ?? 0) === 0 &&
2128
+ Number(d?.totalCreditRefundAmount ?? 0) === 0 &&
2129
+ Number(d?.totalLoyaltyPointsRefundAmount ?? 0) === 0);
2130
+ });
2131
+ /** A real cash-wallet portion is part of the refund (distinct from forfeited loyalty points landing in the wallet). */
2132
+ this.hasCashWalletRefund = computed(() => Number(this.adjustedData()?.walletRefundAmount ?? 0) > 0);
2133
+ /**
2134
+ * Wallet card's title — normally "Almatar Wallet", but the loyalty program
2135
+ * name (Wala, Qitaf, …) when the whole refund is that program's forfeited
2136
+ * balance landing back in the wallet.
2137
+ */
2138
+ this.walletCardLabel = computed(() => this.isLoyaltyOnlyRefund()
2139
+ ? this.walletOrLoyaltyLabel(this.adjustedData()) || 'Almatar Wallet'
2140
+ : 'Almatar Wallet');
2141
+ /**
2142
+ * Part of the refund came from converting external loyalty points into
2143
+ * wallet funds — surfaced as a note under the wallet destination so the
2144
+ * agent knows those points land as permanent wallet balance, not cash.
2145
+ */
2146
+ this.hasLoyaltyPointsRefund = computed(() => Number(this.adjustedData()?.totalLoyaltyPoints ?? 0) > 0);
2147
+ /**
2148
+ * The booking is refundable but nets to 0.00 after deductions — a
2149
+ * dedicated NIL Refund state, distinct from "not refundable": no refund
2150
+ * method to choose between, just confirmation that nothing is owed back.
2151
+ */
2152
+ this.isNilRefund = computed(() => {
2153
+ const d = this.adjustedData();
2154
+ return !!d?.isRefundable && Number(d.totalRefundAmount ?? 0) === 0;
2155
+ });
2156
+ /** Same as `isNilRefund`, scoped to the confirmed cancellation result — drives the result screen's title. */
2157
+ this.confirmIsNilRefund = computed(() => {
2158
+ const d = this.cancelResult();
2159
+ return !!d?.isRefundable && Number(d.totalRefundAmount ?? 0) === 0;
2160
+ });
2161
+ /** Whether the customer may send the whole refund to their Almatar Wallet. */
2162
+ this.isFullWalletRefundAllowed = computed(() => !!this.adjustedData()?.isFullWalletRefundAllowed);
2163
+ /** Whether the customer paid via an installment gateway (Tamara, Tabby, …). */
2164
+ this.isInstallmentMethod = computed(() => INSTALLMENT_GATEWAY_CODES.includes(this.normalizeGatewayCode(this.adjustedData()?.paymentMethodCustomerUsed)));
2165
+ /** Whether the customer paid via a loyalty-program gateway (Al Rajhi Mokafaa, …). */
2166
+ this.isLoyaltyMethod = computed(() => LOYALTY_GATEWAY_CODES.includes(this.normalizeGatewayCode(this.adjustedData()?.paymentMethodCustomerUsed)));
2167
+ /** Any gateway that refunds somewhere other than a plain card — used for the wallet-style icon. */
2168
+ this.isSpecialGatewayMethod = computed(() => this.isInstallmentMethod() || this.isLoyaltyMethod());
2169
+ /**
2170
+ * Card-refund settlement-timeline badge — Tabby and Tamara each have their
2171
+ * own processing window, distinct from a plain card's generic bank policy.
2172
+ */
2173
+ this.cardBadgeText = computed(() => {
2174
+ const code = this.normalizeGatewayCode(this.adjustedData()?.paymentMethodCustomerUsed);
2175
+ if (code === 'TB')
2176
+ return 'From 5-7 business days';
2177
+ if (code === 'TM')
2178
+ return 'Up to 10 business days';
2179
+ if (this.isLoyaltyMethod())
2180
+ return 'Loyalty Program Policy';
2181
+ return 'Up to 15 business days, bank policy';
2182
+ });
2183
+ /** Same as `cardBadgeText`, scoped to the confirmed cancellation result. */
2184
+ this.confirmCardBadgeText = computed(() => {
2185
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2186
+ const d = this.cancelResult();
2187
+ const code = this.normalizeGatewayCode(d?.paymentMethodCustomerUsed);
2188
+ if (code === 'TB')
2189
+ return 'From 5-7 business days';
2190
+ if (code === 'TM')
2191
+ return 'Up to 10 business days';
2192
+ if (LOYALTY_GATEWAY_CODES.includes(code))
2193
+ return 'Loyalty Program Policy';
2194
+ return 'Up to 15 business days, bank policy';
2195
+ });
2196
+ /**
2197
+ * Per-method breakdown of the "Total paid" amount. The backend doesn't
2198
+ * (yet) expose a dedicated original-payment breakdown, so the known
2199
+ * refund-destination fields (card / wallet / loyalty) are used as a proxy
2200
+ * for how the booking was originally paid, and whatever's left over after
2201
+ * those (e.g. a non-refundable leg/fee that never shows up in any refund
2202
+ * field) is folded into the primary payment method — so the rows always
2203
+ * add up to the full `originalAmount` shown above them, instead of only
2204
+ * covering the refundable portion.
2205
+ */
2206
+ this.paidBreakdownRows = computed(() => {
2207
+ const d = this.adjustedData();
2208
+ if (!d)
2209
+ return [];
2210
+ // Card and loyalty stay distinct labels (same categorization as
2211
+ // `refunds`) so a gateway like Al Rajhi Mokafaa doesn't swallow an
2212
+ // actual separate card portion under one label.
2213
+ const cardLabel = (!this.isLoyaltyMethod() && this.customerPaymentLabel()) || 'Original Payment Method';
2214
+ // The wallet destination row always reads "Almatar Wallet" — unlike
2215
+ // `refunds`/`confirmRefunds`, this row never swaps to the loyalty
2216
+ // program name, since `forfeitedLoyaltyLabel` below is that program's
2217
+ // own dedicated row and the two must stay visually distinct here.
2218
+ const walletLabel = 'Almatar Wallet';
2219
+ const forfeitedLoyaltyLabel = this.walletOrLoyaltyLabel(d) || 'Loyalty Points';
2220
+ const loyaltyLabel = this.customerPaymentLabel() || 'Loyalty Points Refund';
2221
+ const amounts = new Map();
2222
+ const add = (label, value) => {
2223
+ if (value <= 0)
2224
+ return;
2225
+ amounts.set(label, (amounts.get(label) ?? 0) + value);
2226
+ };
2227
+ add(cardLabel, Number(d.totalCardRefundAmount ?? 0));
2228
+ // This row shows how much of the *original* payment went through the
2229
+ // wallet — `totalWalletAmount`, not `walletRefundAmount` (a refund
2230
+ // destination amount that can differ, e.g. only part of the wallet
2231
+ // payment is refundable after deductions).
2232
+ add(walletLabel, Number(d.totalWalletAmount ?? 0));
2233
+ add(loyaltyLabel, Number(d.totalLoyaltyPointsRefundAmount ?? 0));
2234
+ // `totalLoyaltyPoints` is the portion of the *original* payment made with
2235
+ // loyalty points (now forfeited/deducted, not refunded) — distinct from
2236
+ // `totalLoyaltyPointsRefundAmount` above and from `walletRefundAmount`
2237
+ // (an unrelated refund-destination amount). Always shown as its own row
2238
+ // under the program name (e.g. "Qitaf"/"Wala") rather than merged into
2239
+ // the generic Wallet row or silently folded into the leftover remainder.
2240
+ add(forfeitedLoyaltyLabel, Number(d.totalLoyaltyPoints ?? 0));
2241
+ const total = Number(d.originalAmount ?? 0);
2242
+ const known = [...amounts.values()].reduce((a, b) => a + b, 0);
2243
+ const remainder = total - known;
2244
+ if (remainder > 0.01) {
2245
+ // Unaccounted leftover (e.g. a non-refundable portion) is folded into
2246
+ // the primary/default payment channel rather than a fabricated 4th row.
2247
+ add(cardLabel, remainder);
2248
+ }
2249
+ return [...amounts.entries()].map(([label, value]) => ({ label, value }));
2250
+ });
2251
+ /** The "Total paid" row always expands — there's always at least one method to show. */
2252
+ this.hasPaidBreakdown = computed(() => this.paidBreakdownRows().length > 0);
2253
+ /**
2254
+ * Display label for `paymentMethodCustomerUsed`, or '' if unknown/internal.
2255
+ * Never surfaces an internal processor name (e.g. Hyperpay) to the agent —
2256
+ * callers fall back to a generic "Original Payment Method" label whenever
2257
+ * this returns ''.
2258
+ */
2259
+ this.customerPaymentLabel = computed(() => {
2260
+ const code = this.normalizeGatewayCode(this.adjustedData()?.paymentMethodCustomerUsed);
2261
+ if (code && INTERNAL_GATEWAY_CODES.includes(code))
2262
+ return '';
2263
+ return code ? PAYMENT_GATEWAY_LABELS[code] : '';
2264
+ });
2265
+ /** Whether the confirmed cancellation's card refund settles via Mada. */
2266
+ this.isMadaMethod = computed(() => {
2267
+ const code = this.normalizeGatewayCode(this.adjustedData()?.paymentMethodCustomerUsed);
2268
+ return !!code && MADA_GATEWAY_CODES.includes(code);
2269
+ });
2270
+ /**
2271
+ * Label for the standalone "Original Payment Method" option card (shown
2272
+ * when the whole refund goes to one destination). Mada wins over
2273
+ * everything else, reading "Mada Card" instead of the generic gateway
2274
+ * name or the "Original Payment Method" fallback.
2275
+ */
2276
+ this.originalPaymentLabel = computed(() => {
2277
+ if (this.isMadaMethod())
2278
+ return 'Mada Card';
2279
+ return this.customerPaymentLabel() || 'Original Payment Method';
2280
+ });
2281
+ /** Same as `isMadaMethod`, scoped to the confirmed cancellation result. */
2282
+ this.confirmIsMadaMethod = computed(() => {
2283
+ const code = this.normalizeGatewayCode(this.cancelResult()?.paymentMethodCustomerUsed);
2284
+ return !!code && MADA_GATEWAY_CODES.includes(code);
2285
+ });
2286
+ /**
2287
+ * Split-refund destination rows — up to four: Almatar Wallet (generic
2288
+ * label), the original payment method (card/installment gateway), a
2289
+ * separate loyalty-points-refund row when the backend sends one, and a
2290
+ * dedicated loyalty-program row (Wala, Qitaf, …) for forfeited points
2291
+ * (`forfeitedLoyaltyPointsFallback`) — kept distinct from the Wallet row
2292
+ * even when both are non-zero.
2293
+ */
2294
+ this.splitRefundRows = computed(() => {
2295
+ const d = this.adjustedData();
2296
+ if (!d)
2297
+ return [];
2298
+ const rows = [];
2299
+ const wallet = Number(d.walletRefundAmount ?? 0);
2300
+ if (wallet > 0) {
2301
+ rows.push({ label: 'Almatar Wallet', value: wallet, instant: true, isLoyalty: false, badgeText: 'Instant' });
2302
+ }
2303
+ const card = Number(d.totalCardRefundAmount ?? 0);
2304
+ if (card > 0) {
2305
+ const badgeText = this.cardBadgeText();
2306
+ rows.push({
2307
+ label: this.isMadaMethod()
2308
+ ? 'Mada Card'
2309
+ : (!this.isLoyaltyMethod() && this.customerPaymentLabel()) || 'Original Payment Method',
2310
+ value: card,
2311
+ instant: false,
2312
+ isLoyalty: false,
2313
+ badgeText,
2314
+ isMada: this.isMadaMethod(),
2315
+ });
2316
+ }
2317
+ const loyalty = Number(d.totalLoyaltyPointsRefundAmount ?? 0);
2318
+ if (loyalty > 0) {
2319
+ rows.push({
2320
+ label: this.customerPaymentLabel() || 'Loyalty Points Refund',
2321
+ value: loyalty,
2322
+ instant: true,
2323
+ isLoyalty: true,
2324
+ badgeText: 'Instant',
2325
+ });
2326
+ }
2327
+ const forfeitedLoyalty = this.forfeitedLoyaltyPointsFallback(d);
2328
+ if (forfeitedLoyalty > 0) {
2329
+ rows.push({
2330
+ label: this.walletOrLoyaltyLabel(d) || 'Loyalty Points',
2331
+ value: forfeitedLoyalty,
2332
+ instant: true,
2333
+ isLoyalty: true,
2334
+ badgeText: 'Instant',
2335
+ });
2336
+ }
2337
+ return rows;
2338
+ });
2339
+ effect(() => {
2340
+ if (this.visible() && this.bookingId()) {
2341
+ this.loadRefund();
2342
+ }
2343
+ else {
2344
+ this.stopHints();
2345
+ this.refundData.set(null);
2346
+ this.cancelResult.set(null);
2347
+ this.onwardChecked.set(true);
2348
+ this.returnChecked.set(true);
2349
+ this.forcedLegType.set(null);
2350
+ this.onwardExpanded.set(true);
2351
+ this.returnExpanded.set(true);
2352
+ this.selectedSource.set('');
2353
+ this.preferredRefundMethod.set('originalPaymentMethod');
2354
+ this.confirming.set(false);
2355
+ this.cancelling.set(false);
2356
+ this.step.set('loading');
2357
+ }
2358
+ }, { allowSignalWrites: true });
2359
+ }
2360
+ loadRefund() {
2361
+ this.loading.set(true);
2362
+ this.startHints();
2363
+ this.bookingService.getRefundDetails(this.bookingId(), this.product()).subscribe({
2364
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2365
+ next: (raw) => {
2366
+ let data = raw?.data ?? raw;
2367
+ this.stopHints();
2368
+ this.refundData.set(data);
2369
+ // Auto-select source: prefer appliedSource, otherwise pick first available
2370
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2371
+ const breakdown = data?.sourceBreakdown ?? {};
2372
+ const sources = Object.keys(breakdown);
2373
+ if (sources.length > 0) {
2374
+ const applied = data?.appliedSource;
2375
+ this.selectedSource.set(applied && breakdown[applied] ? applied : sources[0]);
2376
+ }
2377
+ this.loading.set(false);
2378
+ // Only show the leg picker for a genuine round-trip the backend hasn't
2379
+ // already restricted to a single leg — otherwise go straight to details.
2380
+ if (this.showRoundTripLegCards()) {
2381
+ // Pre-check whichever legs are actually refundable, matching the
2382
+ // consumer app's "non-refundable legs can't be selected" behavior.
2383
+ this.onwardChecked.set(this.onwardRefundable());
2384
+ this.returnChecked.set(this.returnRefundable());
2385
+ this.step.set('select-leg');
2386
+ }
2387
+ else {
2388
+ this.step.set('details');
2389
+ }
2390
+ },
2391
+ error: () => { this.stopHints(); this.loading.set(false); this.toast.error('Could not load refund details.'); },
2392
+ });
2393
+ }
2394
+ /**
2395
+ * Both legs in play — sum each leg's own figures instead of trusting root
2396
+ * (root isn't reliably scoped to the picked alternatives, e.g. rounding
2397
+ * can drift between leg totals and the combined root total).
2398
+ */
2399
+ buildCombinedLegData(root, onward, ret) {
2400
+ const onwardData = this.buildLegData(root, onward, 'onward');
2401
+ const returnData = this.buildLegData(root, ret, 'return');
2402
+ const sum = (key) => Number(onwardData[key] || 0) + Number(returnData[key] || 0);
2403
+ return {
2404
+ ...root,
2405
+ isRefundable: onward.isRefundable === true && ret.isRefundable === true,
2406
+ totalRefundAmount: sum('totalRefundAmount'),
2407
+ totalRefundAmountBeforeCeil: sum('totalRefundAmountBeforeCeil'),
2408
+ totalCardRefundAmount: sum('totalCardRefundAmount'),
2409
+ totalCreditRefundAmount: sum('totalCreditRefundAmount'),
2410
+ totalNonRefundableServiceCharge: sum('totalNonRefundableServiceCharge'),
2411
+ totalCouponAmount: sum('totalCouponAmount'),
2412
+ walletRefundAmount: sum('walletRefundAmount'),
2413
+ totalPenaltyPoints: sum('totalPenaltyPoints'),
2414
+ totalSupplierPenaltyPoints: sum('totalSupplierPenaltyPoints'),
2415
+ totalLoyaltyPoints: sum('totalLoyaltyPoints'),
2416
+ totalGiftAmount: sum('totalGiftAmount'),
2417
+ totalAncillaryRefundAmount: sum('totalAncillaryRefundAmount'),
2418
+ totalLoyaltyPointsRefundAmount: sum('totalLoyaltyPointsRefundAmount'),
2419
+ totalCancellationFee: sum('totalCancellationFee'),
2420
+ totalSupplierBookingAmount: sum('totalSupplierBookingAmount'),
2421
+ originalAmount: Number(root.originalAmount || 0),
2422
+ refundBreakdown: this.mergeBreakdowns(onwardData.refundBreakdown, returnData.refundBreakdown),
2423
+ legInfo: { onward, return: ret },
2424
+ };
2425
+ }
2426
+ /** Combine two legs' itemized breakdowns for the "both legs" combined view. */
2427
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2428
+ mergeBreakdowns(a, b) {
2429
+ if (!a && !b)
2430
+ return undefined;
2431
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2432
+ const mergeSide = (sa, sb) => ({
2433
+ fare: Number(sa?.fare || 0) + Number(sb?.fare || 0) || undefined,
2434
+ addOns: [...(sa?.addOns ?? []), ...(sb?.addOns ?? [])],
2435
+ ancillaries: [...(sa?.ancillaries ?? []), ...(sb?.ancillaries ?? [])],
2436
+ serviceFee: Number(sa?.serviceFee || 0) + Number(sb?.serviceFee || 0) || undefined,
2437
+ handlingFee: Number(sa?.handlingFee || 0) + Number(sb?.handlingFee || 0) || undefined,
2438
+ cancellationPenalty: Number(sa?.cancellationPenalty || 0) + Number(sb?.cancellationPenalty || 0) || undefined,
2439
+ });
2440
+ return {
2441
+ refundable: mergeSide(a?.refundable, b?.refundable),
2442
+ nonRefundable: mergeSide(a?.nonRefundable, b?.nonRefundable),
2443
+ };
2444
+ }
2445
+ buildLegData(root, leg, legType) {
2446
+ // When this leg has no sibling in `legInfo` (a genuine one-way booking,
2447
+ // or a narrowed CrossSeller selection where the API only ever sent the
2448
+ // one leg), root necessarily describes this same single leg — so a
2449
+ // destination total the leg's own object left at 0 can safely fall back
2450
+ // to root's figure instead of silently zeroing out a real refund
2451
+ // destination. With a sibling leg present, root aggregates both legs and
2452
+ // is not trustworthy per-leg, so the leg's own value (even 0) wins.
2453
+ const hasSiblingLeg = legType === 'onward' ? !!root.legInfo?.return : !!root.legInfo?.onward;
2454
+ const fallback = (value, rootValue) => Number(value || (hasSiblingLeg ? 0 : rootValue) || 0);
2455
+ const isCreditShell = leg.isCreditShell === true;
2456
+ const legRefund = Number(leg.totalRefundAmount ?? 0);
2457
+ const cardRefund = isCreditShell ? 0 : fallback(leg.totalCardRefundAmount, root.totalCardRefundAmount);
2458
+ const creditRefund = isCreditShell ? legRefund : fallback(leg.totalCreditRefundAmount, root.totalCreditRefundAmount);
2459
+ // The backend sometimes omits `isRefundable` on a single-leg booking's
2460
+ // leg object entirely (not `false`, just absent) while the root/source
2461
+ // breakdown both say the booking is refundable — trust the root flag
2462
+ // rather than defaulting a missing field to "not eligible for a refund".
2463
+ const legIsRefundable = leg.isRefundable === undefined ? root.isRefundable === true : leg.isRefundable === true;
2464
+ return {
2465
+ ...root,
2466
+ isRefundable: legIsRefundable,
2467
+ totalRefundAmount: legRefund,
2468
+ // Pre-rounding total — the backend ceils `totalRefundAmount` to a whole
2469
+ // currency unit, so wallet+card+credit+loyalty legitimately fall a few
2470
+ // cents short of it; reconciliation must compare against this instead.
2471
+ totalRefundAmountBeforeCeil: Number(leg.totalRefundAmountBeforeCeil ?? leg.totalRefundAmount ?? 0),
2472
+ totalCardRefundAmount: cardRefund,
2473
+ totalCreditRefundAmount: creditRefund,
2474
+ totalNonRefundableServiceCharge: Number(leg.totalNonRefundableServiceCharge || 0),
2475
+ totalCouponAmount: Number(leg.totalCouponAmount || 0),
2476
+ walletRefundAmount: fallback(leg.walletRefundAmount, root.walletRefundAmount),
2477
+ totalPenaltyPoints: Number(leg.totalPenaltyPoints || 0),
2478
+ totalSupplierPenaltyPoints: Number(leg.totalSupplierPenaltyPoints || 0),
2479
+ totalLoyaltyPoints: Number(leg.totalLoyaltyPoints || 0),
2480
+ totalGiftAmount: Number(leg.totalGiftAmount || 0),
2481
+ totalAncillaryRefundAmount: Number(leg.totalAncillaryRefundAmount || 0),
2482
+ totalLoyaltyPointsRefundAmount: fallback(leg.totalLoyaltyPointsRefundAmount, root.totalLoyaltyPointsRefundAmount),
2483
+ totalCancellationFee: Number(leg.totalCancellationFee ?? leg.totalPenaltyPoints ?? 0),
2484
+ totalSupplierBookingAmount: Number(leg.totalSupplierBookingAmount || 0),
2485
+ originalAmount: Number(root.originalAmount || 0),
2486
+ // Leg-scoped breakdown wins when the backend sends one; a single-leg
2487
+ // booking may only carry it at the root instead.
2488
+ refundBreakdown: leg.refundBreakdown ?? root.refundBreakdown,
2489
+ legInfo: legType === 'onward' ? { onward: leg } : { return: leg },
2490
+ };
2491
+ }
2492
+ startHints() {
2493
+ let i = 0;
2494
+ this.cancelHint.set(this.CANCEL_HINTS[0]);
2495
+ this.hintTimer = setInterval(() => {
2496
+ if (i < this.CANCEL_HINTS.length - 1) {
2497
+ i++;
2498
+ this.cancelHint.set(this.CANCEL_HINTS[i]);
2499
+ }
2500
+ else {
2501
+ clearInterval(this.hintTimer);
2502
+ this.hintTimer = null;
2503
+ }
2504
+ }, 1800);
2505
+ }
2506
+ stopHints() {
2507
+ if (this.hintTimer) {
2508
+ clearInterval(this.hintTimer);
2509
+ this.hintTimer = null;
2510
+ }
2511
+ this.cancelHint.set('');
2512
+ }
2513
+ fmt(value, currency) {
2514
+ const cur = currency ?? this.refundData()?.currency ?? 'SAR';
2515
+ return `${this.fmtNum(value)} ${cur}`;
2516
+ }
2517
+ // Plain formatted number without a currency suffix (used by the calc divider / formula box).
2518
+ fmtNum(value) {
2519
+ return Number(value || 0).toLocaleString('en-SA', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
2520
+ }
2521
+ fmtNeg(value, currency) {
2522
+ const n = Number(value || 0);
2523
+ const str = this.fmt(n, currency);
2524
+ return n > 0 ? `− ${str}` : str;
2525
+ }
2526
+ fmtNegResult(value) {
2527
+ const n = Number(value || 0);
2528
+ const str = this.fmtResult(n);
2529
+ return n > 0 ? `− ${str}` : str;
2530
+ }
2531
+ // Entry point from the details screen: always goes to the refund-method
2532
+ // step. For credit-shell refunds that step shows a single read-only credit
2533
+ // notice instead of a picker, since the refund can only go to the shell —
2534
+ // but the agent still sees it before confirming, same as the standard flow.
2535
+ requestCancel() {
2536
+ // NIL refund: nothing to refund at all — skip the refund-method popup
2537
+ // and cancel directly.
2538
+ if (this.isNilRefund()) {
2539
+ this.confirm();
2540
+ }
2541
+ else {
2542
+ this.confirming.set(true);
2543
+ }
2544
+ }
2545
+ confirm() {
2546
+ const sel = this.selectedLeg();
2547
+ // Build legInfo array: omit when cancelling both (backend treats absence as both)
2548
+ const legInfo = sel === 'onward' ? ['onward'] :
2549
+ sel === 'return' ? ['return'] :
2550
+ undefined; // both legs — omit the field
2551
+ const source = this.selectedSource() || undefined;
2552
+ const preferredRefundMethod = this.preferredRefundMethod();
2553
+ const splitAmounts = preferredRefundMethod === 'split'
2554
+ ? {
2555
+ walletRefundAmount: Number(this.adjustedData()?.walletRefundAmount ?? 0),
2556
+ totalCardRefundAmount: Number(this.adjustedData()?.totalCardRefundAmount ?? 0),
2557
+ }
2558
+ : undefined;
2559
+ this.confirming.set(false);
2560
+ this.stopHints();
2561
+ this.cancelling.set(true);
2562
+ this.bookingService.confirmCancel(this.bookingId(), this.product(), legInfo, source, preferredRefundMethod, splitAmounts).subscribe({
2563
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2564
+ next: (raw) => {
2565
+ this.stopHints();
2566
+ this.cancelling.set(false);
2567
+ this.cancelResult.set(raw?.data ?? raw);
2568
+ this.confirmed.emit(sel);
2569
+ },
2570
+ error: () => {
2571
+ this.stopHints();
2572
+ this.cancelling.set(false);
2573
+ this.toast.error('Cancellation failed. Please try again.');
2574
+ },
2575
+ });
2576
+ }
2577
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdCancelDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2578
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SbdCancelDialogComponent, isStandalone: true, selector: "app-sbd-cancel-dialog", inputs: { visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, bookingId: { classPropertyName: "bookingId", publicName: "bookingId", isSignal: true, isRequired: false, transformFunction: null }, product: { classPropertyName: "product", publicName: "product", isSignal: true, isRequired: false, transformFunction: null }, pnrInfo: { classPropertyName: "pnrInfo", publicName: "pnrInfo", isSignal: true, isRequired: false, transformFunction: null }, bookingInfo: { classPropertyName: "bookingInfo", publicName: "bookingInfo", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", confirmed: "confirmed" }, 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-cancel-dialog-wrapper\">\n\n <div class=\"sbd-cancel\">\n\n <!-- Loading refund details \u2014 shows cycling hints -->\n @if (loading()) {\n <div class=\"sbd-cancel__are-you-sure\">\n <button type=\"button\" class=\"sbd-cancel__close-btn sbd-cancel__close-btn--floating\" (click)=\"closed.emit()\" aria-label=\"Close\">\n <i class=\"pi pi-times\"></i>\n </button>\n <div class=\"sbd-cancel__ays-icon\">\n <i class=\"pi pi-spin pi-spinner\"></i>\n </div>\n @for (h of [cancelHint()]; track h) {\n <div class=\"sbd-cancel__ays-hint\">{{ h }}</div>\n }\n <div class=\"sbd-cancel__ays-dots\">\n <span></span><span></span><span></span>\n </div>\n </div>\n }\n\n <!-- Select leg \u2014 shown once the refund preview has fully loaded, before the\n refund breakdown, for round-trip bookings the backend hasn't already\n restricted to a single leg. Same card design as almatar-b2c's\n select-leg screen (checkbox overlay, refundable badge, disclaimer). -->\n @else if (step() === 'select-leg') {\n <div class=\"sbd-cancel__method-header\">\n <button type=\"button\" class=\"sbd-cancel__method-close\" (click)=\"closed.emit()\" aria-label=\"Close\">\n <i class=\"pi pi-times\"></i>\n </button>\n <div class=\"sbd-cancel__method-header-text\">\n <h3 class=\"sbd-cancel__method-title\">Select Leg to Cancel</h3>\n <span class=\"sbd-cancel__method-sub\">Booking ID #{{ bookingId() }}</span>\n </div>\n </div>\n\n <div class=\"sbd-cancel__select-leg-body\">\n @if (forcedLegType(); as forced) {\n <div class=\"sbd-cancel__single-leg-note\">\n <i class=\"pi pi-info-circle\"></i>\n <span>This airline doesn't allow cancelling a single flight \u2014 {{ itineraryLeg(forced)?.from }} to {{ itineraryLeg(forced)?.to }} will be included automatically.</span>\n </div>\n }\n @if (allLegsNonRefundable()) {\n <div class=\"sbd-cancel__all-non-refundable-note\">\n <i class=\"pi pi-exclamation-triangle\"></i>\n <span>Sorry, we're unable to provide a refund for this booking, as both flights are non-refundable based on airline rules.</span>\n </div>\n }\n <!-- Onward -->\n <div class=\"sbd-cancel__leg-select-card\"\n [ngClass]=\"{\n 'sbd-cancel__leg-select-card--selected': onwardChecked() && onwardRefundable(),\n 'sbd-cancel__leg-select-card--disabled': !onwardRefundable(),\n 'sbd-cancel__leg-select-card--frozen': forcedLegType() === 'onward'\n }\">\n @if (forcedLegType() !== 'onward') {\n <label class=\"sbd-cancel__leg-select-checkbox\">\n <input type=\"checkbox\" [checked]=\"onwardChecked()\" [disabled]=\"!onwardRefundable()\"\n (change)=\"toggleLeg('onward')\" />\n <span class=\"sbd-cancel__leg-select-icon\"><i class=\"pi pi-check\"></i></span>\n </label>\n } @else {\n <i class=\"pi pi-lock sbd-cancel__leg-select-lock\"></i>\n }\n <div class=\"sbd-cancel__leg-select-content\">\n @if (itineraryLeg('onward'); as leg) {\n <div class=\"sbd-cancel__leg-select-top\">\n <div>\n <span class=\"sbd-cancel__leg-select-name\">{{ leg.from }} to {{ leg.to }}</span>\n <div class=\"sbd-cancel__leg-select-date\">{{ fmtShortDate(leg.depTime) }}</div>\n </div>\n <span class=\"sbd-cancel__badge sbd-cancel__badge--outline\"\n [ngClass]=\"onwardRefundable() && forcedLegType() !== 'onward' ? 'sbd-cancel__badge--green' : 'sbd-cancel__badge--muted'\">\n {{ onwardRefundable() ? 'Refundable' : 'Non-Refundable' }}\n </span>\n </div>\n <div class=\"sbd-cancel__leg-select-times\">\n <span class=\"sbd-cancel__leg-select-time\">{{ fmtTime(leg.depTime) }}</span>\n <span class=\"sbd-cancel__leg-select-line\">\n <span class=\"sbd-cancel__leg-select-line-stops\">{{ leg.stops.length > 0 ? (leg.stops.length + ' stop' + (leg.stops.length > 1 ? 's' : '')) : 'Direct' }}</span>\n @if (fmtMins(leg.totalMinutes)) { <span class=\"sbd-cancel__leg-select-line-dur\">{{ fmtMins(leg.totalMinutes) }}</span> }\n <i class=\"pi pi-arrow-right sbd-cancel__leg-select-line-arrow\"></i>\n </span>\n <span class=\"sbd-cancel__leg-select-time\">{{ fmtTime(leg.arrTime) }}</span>\n </div>\n @if (leg.airlineName || leg.cabin) {\n <span class=\"sbd-cancel__leg-select-airline\">\n {{ leg.airlineName }}{{ leg.airlineName && leg.cabin ? ' \u00B7 ' : '' }}{{ leg.cabin }}\n </span>\n }\n <div class=\"sbd-cancel__leg-select-footer\">\n @if (leg.operatedBy.length) {\n <span class=\"sbd-cancel__leg-select-operated-by\">Operated by {{ leg.operatedBy.join(', ') }}</span>\n }\n <!-- <i class=\"pi pi-chevron-down sbd-cancel__leg-select-chevron\"></i> -->\n </div>\n } @else {\n <div class=\"sbd-cancel__leg-select-top\">\n <span class=\"sbd-cancel__leg-select-name\">Onward</span>\n <span class=\"sbd-cancel__badge sbd-cancel__badge--outline\"\n [ngClass]=\"onwardRefundable() && forcedLegType() !== 'onward' ? 'sbd-cancel__badge--green' : 'sbd-cancel__badge--muted'\">\n {{ onwardRefundable() ? 'Refundable' : 'Non-Refundable' }}\n </span>\n </div>\n }\n @if (!onwardRefundable()) {\n <div class=\"sbd-cancel__leg-select-notice\">\n <i class=\"pi pi-info-circle\"></i>\n <span>This flight type doesn't allow refunds on cancellation, based on the airline's fare rules.</span>\n </div>\n }\n </div>\n </div>\n\n <!-- Return -->\n <div class=\"sbd-cancel__leg-select-card\"\n [ngClass]=\"{\n 'sbd-cancel__leg-select-card--selected': returnChecked() && returnRefundable(),\n 'sbd-cancel__leg-select-card--disabled': !returnRefundable(),\n 'sbd-cancel__leg-select-card--frozen': forcedLegType() === 'return'\n }\">\n @if (forcedLegType() !== 'return') {\n <label class=\"sbd-cancel__leg-select-checkbox\">\n <input type=\"checkbox\" [checked]=\"returnChecked()\" [disabled]=\"!returnRefundable()\"\n (change)=\"toggleLeg('return')\" />\n <span class=\"sbd-cancel__leg-select-icon\"><i class=\"pi pi-check\"></i></span>\n </label>\n } @else {\n <i class=\"pi pi-lock sbd-cancel__leg-select-lock\"></i>\n }\n <div class=\"sbd-cancel__leg-select-content\">\n @if (itineraryLeg('return'); as leg) {\n <div class=\"sbd-cancel__leg-select-top\">\n <div>\n <span class=\"sbd-cancel__leg-select-name\">{{ leg.from }} to {{ leg.to }}</span>\n <div class=\"sbd-cancel__leg-select-date\">{{ fmtShortDate(leg.depTime) }}</div>\n </div>\n <span class=\"sbd-cancel__badge sbd-cancel__badge--outline\"\n [ngClass]=\"returnRefundable() && forcedLegType() !== 'return' ? 'sbd-cancel__badge--green' : 'sbd-cancel__badge--muted'\">\n {{ returnRefundable() ? 'Refundable' : 'Non-Refundable' }}\n </span>\n </div>\n <div class=\"sbd-cancel__leg-select-times\">\n <span class=\"sbd-cancel__leg-select-time\">{{ fmtTime(leg.depTime) }}</span>\n <span class=\"sbd-cancel__leg-select-line\">\n <span class=\"sbd-cancel__leg-select-line-stops\">{{ leg.stops.length > 0 ? (leg.stops.length + ' stop' + (leg.stops.length > 1 ? 's' : '')) : 'Direct' }}</span>\n @if (fmtMins(leg.totalMinutes)) { <span class=\"sbd-cancel__leg-select-line-dur\">{{ fmtMins(leg.totalMinutes) }}</span> }\n <i class=\"pi pi-arrow-right sbd-cancel__leg-select-line-arrow\"></i>\n </span>\n <span class=\"sbd-cancel__leg-select-time\">{{ fmtTime(leg.arrTime) }}</span>\n </div>\n @if (leg.airlineName || leg.cabin) {\n <span class=\"sbd-cancel__leg-select-airline\">\n {{ leg.airlineName }}{{ leg.airlineName && leg.cabin ? ' \u00B7 ' : '' }}{{ leg.cabin }}\n </span>\n }\n <div class=\"sbd-cancel__leg-select-footer\">\n @if (leg.operatedBy.length) {\n <span class=\"sbd-cancel__leg-select-operated-by\">Operated by {{ leg.operatedBy.join(', ') }}</span>\n }\n <!-- <i class=\"pi pi-chevron-down sbd-cancel__leg-select-chevron\"></i> -->\n </div>\n } @else {\n <div class=\"sbd-cancel__leg-select-top\">\n <span class=\"sbd-cancel__leg-select-name\">Return</span>\n <span class=\"sbd-cancel__badge sbd-cancel__badge--outline\"\n [ngClass]=\"returnRefundable() && forcedLegType() !== 'return' ? 'sbd-cancel__badge--green' : 'sbd-cancel__badge--muted'\">\n {{ returnRefundable() ? 'Refundable' : 'Non-Refundable' }}\n </span>\n </div>\n }\n @if (!returnRefundable()) {\n <div class=\"sbd-cancel__leg-select-notice\">\n <i class=\"pi pi-info-circle\"></i>\n <span>This flight type doesn't allow refunds on cancellation, based on the airline's fare rules.</span>\n </div>\n }\n </div>\n </div>\n </div>\n\n @if (!allLegsNonRefundable() && !onwardChecked() && !returnChecked()) {\n <div class=\"sbd-cancel__leg-select-hint\">Select at least one flight to continue</div>\n }\n\n <div class=\"sbd-cancel__footer\">\n <button pButton\n styleClass=\"sbd-cancel__btn-primary\"\n label=\"Continue\"\n [disabled]=\"allLegsNonRefundable() || (!onwardChecked() && !returnChecked())\"\n (click)=\"step.set('details')\"></button>\n </div>\n }\n\n <!-- Refund method \u2014 replaces the old are-you-sure + radio picker.\n Stays mounted (dimmed) behind the result overlay once confirmed. -->\n @else if (confirming() || cancelResult()) {\n <div class=\"sbd-cancel__method-header\">\n <button type=\"button\" class=\"sbd-cancel__method-close\" (click)=\"confirming.set(false)\" aria-label=\"Close\">\n <i class=\"pi pi-times\"></i>\n </button>\n <div class=\"sbd-cancel__method-header-text\">\n <h3 class=\"sbd-cancel__method-title\">Refund Method</h3>\n <span class=\"sbd-cancel__method-sub\">Booking ID #{{ bookingId() }}</span>\n </div>\n </div>\n\n <div class=\"sbd-cancel__method-body\">\n <div class=\"sbd-cancel__method-amount\">\n <span>You will get back</span>\n <strong>{{ fmt(adjustedData()?.totalRefundAmount) }}</strong>\n </div>\n\n @if (isCreditShell()) {\n <!-- Credit-shell refunds have exactly one destination \u2014 the airline\n credit shell \u2014 so show it as a read-only card, not a picker. -->\n <div class=\"sbd-cancel__method-option sbd-cancel__method-option--active\">\n <span class=\"sbd-cancel__method-icon\"><i class=\"pi pi-wallet\"></i></span>\n <span class=\"sbd-cancel__method-body-text\">\n <span class=\"sbd-cancel__method-name\">{{ creditShellSupplierName() }} website credit</span>\n <span class=\"sbd-cancel__method-desc\">Your refund will be issued instantly by {{ creditShellSupplierName() }}.</span>\n <span class=\"sbd-cancel__method-badge sbd-cancel__method-badge--instant\">\n <svg width=\"12\" height=\"9\" viewBox=\"0 0 12 9\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"M7.31818 0C8.42727 0 9.49093 0.440583 10.2752 1.22483C11.0594 2.00907 11.5 3.07273 11.5 4.18182C11.5 5.2909 11.0594 6.35457 10.2752 7.13881C9.49093 7.92305 8.42727 8.36364 7.31818 8.36364C6.20909 8.36364 5.14543 7.92305 4.36119 7.13881C3.57695 6.35457 3.13636 5.2909 3.13636 4.18182C3.13636 3.07273 3.57695 2.00907 4.36119 1.22483C5.14543 0.440583 6.20909 0 7.31818 0ZM7.31818 1.04545C6.48637 1.04545 5.68862 1.37589 5.10044 1.96407C4.51226 2.55226 4.18182 3.35 4.18182 4.18182C4.18182 5.01363 4.51226 5.81138 5.10044 6.39956C5.68862 6.98774 6.48637 7.31818 7.31818 7.31818C8.15 7.31818 8.94774 6.98774 9.53593 6.39956C10.1241 5.81138 10.4545 5.01363 10.4545 4.18182C10.4545 3.35 10.1241 2.55226 9.53593 1.96407C8.94774 1.37589 8.15 1.04545 7.31818 1.04545ZM6.79545 2.09091H7.57955V4.06682L8.7975 5.28477L8.24341 5.83886L6.79545 4.39091V2.09091ZM0.522727 7.31818C0.384091 7.31818 0.251134 7.26311 0.153103 7.16508C0.0550729 7.06705 0 6.93409 0 6.79545C0 6.65682 0.0550729 6.52386 0.153103 6.42583C0.251134 6.3278 0.384091 6.27273 0.522727 6.27273H2.52477C2.68682 6.64386 2.89591 6.99409 3.13636 7.31818H0.522727ZM1.04545 4.70455C0.906819 4.70455 0.773861 4.64947 0.67583 4.55144C0.5778 4.45341 0.522727 4.32045 0.522727 4.18182C0.522727 4.04318 0.5778 3.91022 0.67583 3.81219C0.773861 3.71416 0.906819 3.65909 1.04545 3.65909H2.11705L2.09091 4.18182L2.11705 4.70455H1.04545ZM1.56818 2.09091C1.42955 2.09091 1.29659 2.03584 1.19856 1.93781C1.10053 1.83978 1.04545 1.70682 1.04545 1.56818C1.04545 1.42955 1.10053 1.29659 1.19856 1.19856C1.29659 1.10053 1.42955 1.04545 1.56818 1.04545H3.13636C2.89591 1.36955 2.68682 1.71977 2.52477 2.09091H1.56818Z\"\n fill=\"#15803C\"\n />\n </svg>\n Instant\n </span>\n </span>\n </div>\n\n <div class=\"sbd-cancel__notice sbd-cancel__notice--credit\">\n <i class=\"pi pi-info-circle\"></i>\n <span>\n Your refund will be issued as airline credit under the same PNR.\n It can be used only for future booking on {{ creditShellSupplierName() }} website.\n </span>\n </div>\n\n <!-- Refundable ancillaries (baggage, seats, \u2026) can independently be\n flagged credit-shell or not \u2014 each renders as its own card,\n matching either the credit-shell style above or the normal\n wallet style, both with the same nested note when credit-shell. -->\n @for (item of creditShellAncillaryItems(); track item.name) {\n <div class=\"sbd-cancel__method-option sbd-cancel__method-option--active\">\n <span class=\"sbd-cancel__method-icon\"><i class=\"pi pi-wallet\"></i></span>\n <span class=\"sbd-cancel__method-body-text\">\n <span class=\"sbd-cancel__method-name\">\n {{ item.isCreditShell ? (creditShellSupplierName() + ' website credit') : 'Almatar Wallet' }}\n \u2014 {{ item.name }}\n </span>\n <span class=\"sbd-cancel__method-desc\">\n @if (item.isCreditShell) {\n Your refund will be issued instantly by {{ creditShellSupplierName() }}.\n } @else {\n Usable for future bookings\n }\n </span>\n <span class=\"sbd-cancel__method-badge sbd-cancel__method-badge--instant\">\n <svg width=\"12\" height=\"9\" viewBox=\"0 0 12 9\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"M7.31818 0C8.42727 0 9.49093 0.440583 10.2752 1.22483C11.0594 2.00907 11.5 3.07273 11.5 4.18182C11.5 5.2909 11.0594 6.35457 10.2752 7.13881C9.49093 7.92305 8.42727 8.36364 7.31818 8.36364C6.20909 8.36364 5.14543 7.92305 4.36119 7.13881C3.57695 6.35457 3.13636 5.2909 3.13636 4.18182C3.13636 3.07273 3.57695 2.00907 4.36119 1.22483C5.14543 0.440583 6.20909 0 7.31818 0ZM7.31818 1.04545C6.48637 1.04545 5.68862 1.37589 5.10044 1.96407C4.51226 2.55226 4.18182 3.35 4.18182 4.18182C4.18182 5.01363 4.51226 5.81138 5.10044 6.39956C5.68862 6.98774 6.48637 7.31818 7.31818 7.31818C8.15 7.31818 8.94774 6.98774 9.53593 6.39956C10.1241 5.81138 10.4545 5.01363 10.4545 4.18182C10.4545 3.35 10.1241 2.55226 9.53593 1.96407C8.94774 1.37589 8.15 1.04545 7.31818 1.04545ZM6.79545 2.09091H7.57955V4.06682L8.7975 5.28477L8.24341 5.83886L6.79545 4.39091V2.09091ZM0.522727 7.31818C0.384091 7.31818 0.251134 7.26311 0.153103 7.16508C0.0550729 7.06705 0 6.93409 0 6.79545C0 6.65682 0.0550729 6.52386 0.153103 6.42583C0.251134 6.3278 0.384091 6.27273 0.522727 6.27273H2.52477C2.68682 6.64386 2.89591 6.99409 3.13636 7.31818H0.522727ZM1.04545 4.70455C0.906819 4.70455 0.773861 4.64947 0.67583 4.55144C0.5778 4.45341 0.522727 4.32045 0.522727 4.18182C0.522727 4.04318 0.5778 3.91022 0.67583 3.81219C0.773861 3.71416 0.906819 3.65909 1.04545 3.65909H2.11705L2.09091 4.18182L2.11705 4.70455H1.04545ZM1.56818 2.09091C1.42955 2.09091 1.29659 2.03584 1.19856 1.93781C1.10053 1.83978 1.04545 1.70682 1.04545 1.56818C1.04545 1.42955 1.10053 1.29659 1.19856 1.19856C1.29659 1.10053 1.42955 1.04545 1.56818 1.04545H3.13636C2.89591 1.36955 2.68682 1.71977 2.52477 2.09091H1.56818Z\"\n fill=\"#15803C\"\n />\n </svg>\n Instant\n </span>\n @if (item.isCreditShell) {\n <span class=\"sbd-cancel__notice sbd-cancel__notice--info\">\n <i class=\"pi pi-info-circle\"></i>\n <span>Your refund will be issued as airline credit under the same PNR. It can be used only for future booking on {{ creditShellSupplierName() }} website.</span>\n </span>\n }\n </span>\n <strong>{{ fmt(item.amount) }}</strong>\n </div>\n }\n } @else {\n\n @if (isFullWalletRefundAllowed() || isWalletOnlyRefund() || isLoyaltyOnlyRefund()) {\n <button type=\"button\" class=\"sbd-cancel__method-option\"\n [ngClass]=\"{ 'sbd-cancel__method-option--active': preferredRefundMethod() === 'wallet' }\"\n (click)=\"preferredRefundMethod.set('wallet')\">\n <span class=\"sbd-cancel__method-icon\"><i class=\"pi pi-wallet\"></i></span>\n <span class=\"sbd-cancel__method-body-text\">\n <span class=\"sbd-cancel__method-name\">{{ walletCardLabel() }}</span>\n <span class=\"sbd-cancel__method-desc\">Usable for future bookings</span>\n <span class=\"sbd-cancel__method-badge sbd-cancel__method-badge--instant\">\n <svg width=\"12\" height=\"9\" viewBox=\"0 0 12 9\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"M7.31818 0C8.42727 0 9.49093 0.440583 10.2752 1.22483C11.0594 2.00907 11.5 3.07273 11.5 4.18182C11.5 5.2909 11.0594 6.35457 10.2752 7.13881C9.49093 7.92305 8.42727 8.36364 7.31818 8.36364C6.20909 8.36364 5.14543 7.92305 4.36119 7.13881C3.57695 6.35457 3.13636 5.2909 3.13636 4.18182C3.13636 3.07273 3.57695 2.00907 4.36119 1.22483C5.14543 0.440583 6.20909 0 7.31818 0ZM7.31818 1.04545C6.48637 1.04545 5.68862 1.37589 5.10044 1.96407C4.51226 2.55226 4.18182 3.35 4.18182 4.18182C4.18182 5.01363 4.51226 5.81138 5.10044 6.39956C5.68862 6.98774 6.48637 7.31818 7.31818 7.31818C8.15 7.31818 8.94774 6.98774 9.53593 6.39956C10.1241 5.81138 10.4545 5.01363 10.4545 4.18182C10.4545 3.35 10.1241 2.55226 9.53593 1.96407C8.94774 1.37589 8.15 1.04545 7.31818 1.04545ZM6.79545 2.09091H7.57955V4.06682L8.7975 5.28477L8.24341 5.83886L6.79545 4.39091V2.09091ZM0.522727 7.31818C0.384091 7.31818 0.251134 7.26311 0.153103 7.16508C0.0550729 7.06705 0 6.93409 0 6.79545C0 6.65682 0.0550729 6.52386 0.153103 6.42583C0.251134 6.3278 0.384091 6.27273 0.522727 6.27273H2.52477C2.68682 6.64386 2.89591 6.99409 3.13636 7.31818H0.522727ZM1.04545 4.70455C0.906819 4.70455 0.773861 4.64947 0.67583 4.55144C0.5778 4.45341 0.522727 4.32045 0.522727 4.18182C0.522727 4.04318 0.5778 3.91022 0.67583 3.81219C0.773861 3.71416 0.906819 3.65909 1.04545 3.65909H2.11705L2.09091 4.18182L2.11705 4.70455H1.04545ZM1.56818 2.09091C1.42955 2.09091 1.29659 2.03584 1.19856 1.93781C1.10053 1.83978 1.04545 1.70682 1.04545 1.56818C1.04545 1.42955 1.10053 1.29659 1.19856 1.19856C1.29659 1.10053 1.42955 1.04545 1.56818 1.04545H3.13636C2.89591 1.36955 2.68682 1.71977 2.52477 2.09091H1.56818Z\"\n fill=\"#15803C\"\n />\n </svg>\n Instant Refund\n </span>\n @if (hasLoyaltyPointsRefund() && !hasCashWalletRefund()) {\n <span class=\"sbd-cancel__notice sbd-cancel__notice--info\">\n <i class=\"pi pi-info-circle\"></i>\n <span>External loyalty points are refunded to your almatar Wallet as permanent points and ready to use in your next booking.</span>\n </span>\n }\n </span>\n <span class=\"sbd-cancel__method-radio\"></span>\n </button>\n }\n\n <!-- Original payment method \u2014 replaced with a \"Split Refund\" card whenever\n part of the refund is forced to the wallet (e.g. loyalty points used),\n since the total can't go 100% to the original payment method.\n Name/desc/badge/icon become gateway-specific for installment (Tamara,\n Tabby) and loyalty (Al Rajhi Mokafaa) payment methods. -->\n @if (!hasSplitRefund() && !isWalletOnlyRefund() && !isLoyaltyOnlyRefund()) {\n <button type=\"button\" class=\"sbd-cancel__method-option\"\n [ngClass]=\"{ 'sbd-cancel__method-option--active': preferredRefundMethod() === 'originalPaymentMethod' }\"\n (click)=\"preferredRefundMethod.set('originalPaymentMethod')\">\n <span class=\"sbd-cancel__method-icon\">\n <i class=\"pi\" [ngClass]=\"isSpecialGatewayMethod() ? 'pi-wallet' : 'pi-credit-card'\"></i>\n </span>\n <span class=\"sbd-cancel__method-body-text\">\n <span class=\"sbd-cancel__method-name\">{{ originalPaymentLabel() }}</span>\n <span class=\"sbd-cancel__method-desc\">\n @if (isInstallmentMethod()) {\n Returned to your {{ customerPaymentLabel() }} installment plan\n } @else if (isLoyaltyMethod()) {\n Refunded to your {{ customerPaymentLabel() }} account\n } @else {\n Returned to your original payment method\n }\n </span>\n <span class=\"sbd-cancel__method-badge\" [ngClass]=\"isLoyaltyMethod() ? 'sbd-cancel__method-badge--loyalty' : 'sbd-cancel__method-badge--days'\">\n @if (!isLoyaltyMethod()) {\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"M3.30078 6.50195C3.20312 6.50195 3.12174 6.4694 3.05664 6.4043C2.99154 6.33919 2.95898 6.25781 2.95898 6.16016C2.95898 6.06576 2.99154 5.986 3.05664 5.9209C3.12174 5.85579 3.20312 5.82324 3.30078 5.82324H5.5127V2.86914C5.5127 2.77474 5.54525 2.69499 5.61035 2.62988C5.67546 2.56478 5.75521 2.53223 5.84961 2.53223C5.94727 2.53223 6.02865 2.56478 6.09375 2.62988C6.15885 2.69499 6.19141 2.77474 6.19141 2.86914V6.16016C6.19141 6.25781 6.15885 6.33919 6.09375 6.4043C6.02865 6.4694 5.94727 6.50195 5.84961 6.50195H3.30078ZM5.85449 10.9551C5.16764 10.9551 4.52311 10.8249 3.9209 10.5645C3.31868 10.3073 2.78971 9.95085 2.33398 9.49512C1.87826 9.03939 1.52018 8.51042 1.25977 7.9082C1.0026 7.30599 0.874023 6.66146 0.874023 5.97461C0.874023 5.28776 1.0026 4.64323 1.25977 4.04102C1.52018 3.4388 1.87826 2.90983 2.33398 2.4541C2.78971 1.99512 3.31868 1.63704 3.9209 1.37988C4.52311 1.12272 5.16764 0.994141 5.85449 0.994141C6.54134 0.994141 7.18587 1.12272 7.78809 1.37988C8.3903 1.63704 8.91927 1.99512 9.375 2.4541C9.83073 2.90983 10.1872 3.4388 10.4443 4.04102C10.7048 4.64323 10.835 5.28776 10.835 5.97461C10.835 6.66146 10.7048 7.30599 10.4443 7.9082C10.1872 8.51042 9.83073 9.03939 9.375 9.49512C8.91927 9.95085 8.3903 10.3073 7.78809 10.5645C7.18587 10.8249 6.54134 10.9551 5.85449 10.9551ZM5.85449 10.125C6.42741 10.125 6.96452 10.0176 7.46582 9.80273C7.96712 9.58789 8.4082 9.29004 8.78906 8.90918C9.16992 8.52832 9.46777 8.08724 9.68262 7.58594C9.89746 7.08464 10.0049 6.54753 10.0049 5.97461C10.0049 5.40169 9.89746 4.86458 9.68262 4.36328C9.46777 3.85872 9.16992 3.41764 8.78906 3.04004C8.4082 2.65918 7.96712 2.36133 7.46582 2.14648C6.96452 1.93164 6.42741 1.82422 5.85449 1.82422C5.28158 1.82422 4.74447 1.93164 4.24316 2.14648C3.74186 2.36133 3.30078 2.65918 2.91992 3.04004C2.53906 3.41764 2.24121 3.85872 2.02637 4.36328C1.81152 4.86458 1.7041 5.40169 1.7041 5.97461C1.7041 6.54753 1.81152 7.08464 2.02637 7.58594C2.24121 8.08724 2.53906 8.52832 2.91992 8.90918C3.30078 9.29004 3.74186 9.58789 4.24316 9.80273C4.74447 10.0176 5.28158 10.125 5.85449 10.125Z\"\n fill=\"#0B54C2\"\n />\n </svg>\n } @else {\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <circle cx=\"6\" cy=\"6\" r=\"5\" stroke=\"#0B54C2\" stroke-width=\"1.2\" />\n <path d=\"M6 5.5v3M6 3.9v.01\" stroke=\"#0B54C2\" stroke-width=\"1.2\" stroke-linecap=\"round\" />\n </svg>\n }\n {{ cardBadgeText() }}\n </span>\n </span>\n <span class=\"sbd-cancel__option-end\">\n @if (isMadaMethod()) {\n <span class=\"sbd-cancel__mada-logo\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"madaLogo\"></ng-container>\n </span>\n }\n <span class=\"sbd-cancel__method-radio\"></span>\n </span>\n </button>\n }\n\n <!-- Split Refund -->\n @if (hasSplitRefund()) {\n <button type=\"button\" class=\"sbd-cancel__method-option sbd-cancel__method-option--split\"\n [ngClass]=\"{ 'sbd-cancel__method-option--active': preferredRefundMethod() === 'split' }\"\n (click)=\"preferredRefundMethod.set('split')\">\n <span class=\"sbd-cancel__method-icon\"><i class=\"pi pi-wallet\"></i></span>\n <span class=\"sbd-cancel__method-body-text\">\n <span class=\"sbd-cancel__method-name\">Split Refund</span>\n <span class=\"sbd-cancel__method-desc\">Returned to your original payment method</span>\n </span>\n <strong class=\"sbd-cancel__method-split-total\">{{ fmt(adjustedData()?.totalRefundAmount) }}</strong>\n <span class=\"sbd-cancel__method-radio\"></span>\n\n <span class=\"sbd-cancel__method-split-breakdown\">\n @for (row of splitRefundRows(); track row.label) {\n <span class=\"sbd-cancel__method-split-row\">\n <span class=\"sbd-cancel__method-split-label\">\n {{ row.label }}\n @if (row.isMada) {\n <span class=\"sbd-cancel__mada-logo\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"madaLogo\"></ng-container>\n </span>\n }\n </span>\n <strong>{{ fmt(row.value) }}</strong>\n </span>\n <span class=\"sbd-cancel__method-badge\"\n [ngClass]=\"row.instant ? 'sbd-cancel__method-badge--instant' : (isLoyaltyMethod() ? 'sbd-cancel__method-badge--loyalty' : 'sbd-cancel__method-badge--days')\">\n {{ row.badgeText }}\n </span>\n @if (row.isLoyalty) {\n <span class=\"sbd-cancel__notice sbd-cancel__notice--info\">\n <i class=\"pi pi-info-circle\"></i>\n <span>External loyalty points are refunded to your almatar Wallet as permanent points and ready to use in your next booking.</span>\n </span>\n }\n }\n </span>\n </button>\n }\n }\n </div>\n\n <div class=\"sbd-cancel__footer\">\n <button pButton\n styleClass=\"sbd-cancel__btn-danger-pill\"\n [label]=\"cancelling() ? 'Cancelling...' : 'Confirm Cancellation'\"\n [disabled]=\"cancelling()\" (click)=\"confirm()\"></button>\n </div>\n }\n\n <!-- Main card -->\n @else if (refundData() && step() === 'details') {\n <!-- Card header -->\n <div class=\"sbd-cancel__header sbd-cancel__header--stack\">\n <div class=\"sbd-cancel__header-titles\">\n <span class=\"sbd-cancel__header-title\">Cancel Booking Request</span>\n <span class=\"sbd-cancel__header-sub\">#{{ refundData()?.bookingId ?? bookingId() }}</span>\n </div>\n <button type=\"button\" class=\"sbd-cancel__close-btn\" (click)=\"closed.emit()\" aria-label=\"Close\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n\n <div class=\"sbd-cancel__body\">\n\n <!-- Non-refundable -->\n @if (!refundData()?.isRefundable) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--warning\">\n <i class=\"pi pi-exclamation-triangle\"></i>\n <div>\n <strong>This booking is not eligible for a refund.</strong>\n @if (refundData()?.cause) {\n <p>{{ refundData()?.cause }}</p>\n } @else {\n <p>Please escalate to the relevant team for manual handling.</p>\n }\n </div>\n </div>\n }\n\n <!-- Refundable flow -->\n @else {\n\n <!-- Top summary: paid / get back -->\n <div class=\"sbd-cancel__summary\">\n <div class=\"sbd-cancel__summary-row\"\n [ngClass]=\"{ 'sbd-cancel__summary-row--toggle': hasPaidBreakdown() && !!adjustedData()?.totalWalletAmount }\"\n (click)=\"hasPaidBreakdown() && !!adjustedData()?.totalWalletAmount && togglePaidSummary()\">\n <span class=\"sbd-cancel__summary-label\"><i class=\"pi pi-wallet\"></i> Total paid</span>\n <span class=\"sbd-cancel__summary-value\">\n <strong>{{ fmt(adjustedData()?.originalAmount) }}</strong>\n @if (hasPaidBreakdown() && !!adjustedData()?.totalWalletAmount) {\n <button type=\"button\" class=\"sbd-cancel__leg-chevron\"\n [ngClass]=\"{ 'sbd-cancel__leg-chevron--collapsed': !paidSummaryExpanded() }\"\n aria-label=\"Toggle paid breakdown\">\n <i class=\"pi pi-chevron-up\"></i>\n </button>\n }\n </span>\n </div>\n\n @if (hasPaidBreakdown() && paidSummaryExpanded() && !!adjustedData()?.totalWalletAmount) {\n <div class=\"sbd-cancel__summary-breakdown\">\n @for (row of paidBreakdownRows(); track row.label) {\n @if (row.label === 'Almatar Wallet') {\n <div class=\"sbd-cancel__summary-breakdown-row\">\n <span>{{ row.label }}</span>\n <span>{{ fmt(row.value) }}</span>\n </div>\n }\n }\n </div>\n }\n\n <div class=\"sbd-cancel__summary-row sbd-cancel__summary-row--refund\">\n <span class=\"sbd-cancel__summary-label\"><i class=\"pi pi-dollar\"></i> You will get back</span>\n <strong>{{ fmt(adjustedData()?.totalRefundAmount) }}</strong>\n </div>\n </div>\n\n <!-- Golden Guarantee gift \u2014 overrides credit-shell messaging below -->\n @if (hasGoldenGuaranteeGift()) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--success\">\n <i class=\"pi pi-shield\"></i>\n <span>You're covered by Cancel for any reason service, you'll get 100% refund, minus any applicable fees. See the breakdown below.</span>\n </div>\n }\n\n <!-- One-way (or a round-trip restricted to a single leg): single card -->\n @if (!showRoundTripLegCards()) {\n <!-- Source selector (multiple suppliers) -->\n @if (availableSources().length > 1) {\n <div class=\"sbd-cancel__source-selector\">\n <span class=\"sbd-cancel__source-label\">Cancel via</span>\n @for (src of availableSources(); track src) {\n <button class=\"sbd-cancel__source-chip\"\n [ngClass]=\"{\n 'sbd-cancel__source-chip--active': selectedSource() === src,\n 'sbd-cancel__source-chip--no-refund': activeSourceBreakdown()?.[src]?.isRefundable === false\n }\"\n (click)=\"selectedSource.set(src)\">\n {{ src }}\n @if (activeSourceBreakdown()?.[src]?.isRefundable === false) {\n <i class=\"pi pi-ban\" style=\"font-size:10px; margin-left:4px;\"></i>\n }\n </button>\n }\n </div>\n @if (sourceNonRefundableHint()) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--warning\">\n <i class=\"pi pi-ban\"></i>\n <span>{{ sourceNonRefundableHint() }}</span>\n </div>\n }\n }\n\n <!-- Not refundable -->\n @if (!currentData()?.isRefundable) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--warning\">\n <i class=\"pi pi-ban\"></i>\n <div>\n <strong>This leg is not eligible for a refund.</strong>\n <p>Please escalate to the relevant team for manual handling.</p>\n </div>\n </div>\n } @else {\n <div class=\"sbd-cancel__section-title\">Refund Details</div>\n\n <div class=\"sbd-cancel__refund-card\">\n <span class=\"sbd-cancel__badge sbd-cancel__badge--outline sbd-cancel__badge--green\">Refundable</span>\n\n @if (refundableBreakdownRows(adjustedData()).length) {\n @for (r of refundableBreakdownRows(adjustedData()); track r.label) {\n <div class=\"sbd-cancel__refund-card-row\">\n <span>{{ r.label }}</span>\n <strong>{{ fmt(r.value) }}</strong>\n </div>\n }\n } @else {\n <div class=\"sbd-cancel__refund-card-row\">\n <span>Fare</span>\n <strong>{{ fmt(adjustedData()?.originalAmount) }}</strong>\n </div>\n }\n\n <!-- hide addon note new CR from CX -->\n <!-- @if (hasRefundableAddonNote(adjustedData())) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--neutral\">\n <i class=\"pi pi-info-circle\"></i>\n <span>Your add-on service/s refund will be processed separately by our Customer Service team shortly.</span>\n </div>\n } -->\n\n <!-- Deductions -->\n @if (deductions().length) {\n <div class=\"sbd-cancel__refund-card-divider\"></div>\n <span class=\"sbd-cancel__badge sbd-cancel__badge--outline sbd-cancel__badge--muted\">Not Refunded - Deductions</span>\n @for (d of deductions(); track d.label) {\n <div class=\"sbd-cancel__refund-card-line\">\n <span>\n {{ d.label }}\n @if (d.badge) { <span class=\"sbd-cancel__tag\">{{ d.badge }}</span> }\n </span>\n <span class=\"sbd-cancel__refund-card-line--neg\">{{ fmtNeg(d.value) }}</span>\n </div>\n }\n }\n\n <div class=\"sbd-cancel__refund-card-divider sbd-cancel__refund-card-divider--dashed\"></div>\n <div class=\"sbd-cancel__refund-card-total\">\n <span>Total Refund</span>\n <strong>{{ fmt(adjustedData()?.totalRefundAmount) }}</strong>\n </div>\n </div>\n\n <!-- Credit shell -->\n @if (creditShellNote()) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--credit\">\n <i class=\"pi pi-wallet\"></i>\n <span>\u2139\uFE0F {{ creditShellNote() }}</span>\n </div>\n }\n }\n }\n\n <!-- Round trip: one collapsible card per leg (only when both legs are cancellable) -->\n @if (showRoundTripLegCards()) {\n <!-- Gift hint -->\n @if (giftHint()) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--tip\">\n <i class=\"pi pi-info-circle\"></i>\n <span>{{ giftHint() }}</span>\n </div>\n }\n\n <!-- Mixed hint -->\n @if (mixedHint()) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--warning\">\n <i class=\"pi pi-exclamation-triangle\"></i>\n <span>{{ mixedHint() }}</span>\n </div>\n }\n\n <!-- Source selector (multiple suppliers) -->\n @if (availableSources().length > 1) {\n <div class=\"sbd-cancel__source-selector\">\n <span class=\"sbd-cancel__source-label\">Cancel via</span>\n @for (src of availableSources(); track src) {\n <button class=\"sbd-cancel__source-chip\"\n [ngClass]=\"{\n 'sbd-cancel__source-chip--active': selectedSource() === src,\n 'sbd-cancel__source-chip--no-refund': activeSourceBreakdown()?.[src]?.isRefundable === false\n }\"\n (click)=\"selectedSource.set(src)\">\n {{ src }}\n @if (activeSourceBreakdown()?.[src]?.isRefundable === false) {\n <i class=\"pi pi-ban\" style=\"font-size:10px; margin-left:4px;\"></i>\n }\n </button>\n }\n </div>\n @if (sourceNonRefundableHint()) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--warning\">\n <i class=\"pi pi-ban\"></i>\n <span>{{ sourceNonRefundableHint() }}</span>\n </div>\n }\n }\n\n <!-- Onward leg card -->\n @if (onwardChecked()) {\n <div class=\"sbd-cancel__leg-card\">\n <div class=\"sbd-cancel__leg-card-header\" (click)=\"toggleLegExpanded('onward')\">\n <span class=\"sbd-cancel__leg-checkbox\">Onward</span>\n <button type=\"button\" class=\"sbd-cancel__leg-chevron\"\n [ngClass]=\"{ 'sbd-cancel__leg-chevron--collapsed': !onwardExpanded() }\"\n aria-label=\"Toggle\">\n <i class=\"pi pi-chevron-up\"></i>\n </button>\n </div>\n\n @if (onwardExpanded()) {\n <div class=\"sbd-cancel__leg-card-body\">\n <span class=\"sbd-cancel__badge sbd-cancel__badge--outline\"\n [ngClass]=\"legAdjustedData('onward')?.isRefundable ? 'sbd-cancel__badge--green' : 'sbd-cancel__badge--red'\">\n {{ legAdjustedData('onward')?.isRefundable ? 'Refundable' : 'Non-Refundable' }}\n </span>\n\n @if (legAdjustedData('onward')?.isRefundable) {\n @if (refundableBreakdownRows(legAdjustedData('onward')).length) {\n @for (r of refundableBreakdownRows(legAdjustedData('onward')); track r.label) {\n <div class=\"sbd-cancel__refund-card-row\">\n <span>{{ r.label }}</span>\n <strong>{{ fmt(r.value) }}</strong>\n </div>\n }\n } @else {\n <div class=\"sbd-cancel__refund-card-row\">\n <span>Fare</span>\n <strong>{{ fmt(legRefundPool('onward')) }}</strong>\n </div>\n }\n\n <!-- hide addon note new CR from CX -->\n <!-- @if (hasRefundableAddonNote(legAdjustedData('onward'))) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--neutral\">\n <i class=\"pi pi-info-circle\"></i>\n <span>Your add-on service/s refund will be processed separately by our Customer Service team shortly.</span>\n </div>\n } -->\n\n @if (legDeductions('onward').length && !isCreditShell()) {\n <div class=\"sbd-cancel__refund-card-divider\"></div>\n <span class=\"sbd-cancel__badge sbd-cancel__badge--outline sbd-cancel__badge--muted\">Not Refunded - Deductions</span>\n @for (d of legDeductions('onward'); track d.label) {\n <div class=\"sbd-cancel__refund-card-line\">\n <span>\n {{ d.label }}\n @if (d.badge) { <span class=\"sbd-cancel__tag\">{{ d.badge }}</span> }\n </span>\n <span class=\"sbd-cancel__refund-card-line--neg\">{{ fmtNeg(d.value) }}</span>\n </div>\n }\n }\n\n <div class=\"sbd-cancel__refund-card-divider sbd-cancel__refund-card-divider--dashed\"></div>\n <div class=\"sbd-cancel__refund-card-total\">\n <span>Departure Total Refund</span>\n <strong>{{ fmt(legAdjustedData('onward')?.totalRefundAmount) }}</strong>\n </div>\n\n @if (refundData()?.legInfo?.onward?.isCreditShell && !hasGoldenGuaranteeGift()) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--credit\">\n <i class=\"pi pi-wallet\"></i>\n <span>\u2139\uFE0F The refund for the onward flight has been issued as an Airline Credit Shell.</span>\n </div>\n }\n } @else {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--warning\">\n <i class=\"pi pi-ban\"></i>\n <span>This leg is not eligible for a refund.</span>\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Return leg card -->\n @if (returnChecked()) {\n <div class=\"sbd-cancel__leg-card\">\n <div class=\"sbd-cancel__leg-card-header\" (click)=\"toggleLegExpanded('return')\">\n <span class=\"sbd-cancel__leg-checkbox\">Return</span>\n <button type=\"button\" class=\"sbd-cancel__leg-chevron\"\n [ngClass]=\"{ 'sbd-cancel__leg-chevron--collapsed': !returnExpanded() }\"\n aria-label=\"Toggle\">\n <i class=\"pi pi-chevron-up\"></i>\n </button>\n </div>\n\n @if (returnExpanded()) {\n <div class=\"sbd-cancel__leg-card-body\">\n <span class=\"sbd-cancel__badge sbd-cancel__badge--outline\"\n [ngClass]=\"legAdjustedData('return')?.isRefundable ? 'sbd-cancel__badge--green' : 'sbd-cancel__badge--red'\">\n {{ legAdjustedData('return')?.isRefundable ? 'Refundable' : 'Non-Refundable' }}\n </span>\n\n @if (legAdjustedData('return')?.isRefundable) {\n @if (refundableBreakdownRows(legAdjustedData('return')).length) {\n @for (r of refundableBreakdownRows(legAdjustedData('return')); track r.label) {\n <div class=\"sbd-cancel__refund-card-row\">\n <span>{{ r.label }}</span>\n <strong>{{ fmt(r.value) }}</strong>\n </div>\n }\n } @else {\n <div class=\"sbd-cancel__refund-card-row\">\n <span>Fare</span>\n <strong>{{ fmt(legRefundPool('return')) }}</strong>\n </div>\n }\n\n <!-- hide addon note new CR from CX -->\n <!-- @if (hasRefundableAddonNote(legAdjustedData('return'))) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--neutral\">\n <i class=\"pi pi-info-circle\"></i>\n <span>Your add-on service/s refund will be processed separately by our Customer Service team shortly.</span>\n </div>\n } -->\n\n @if (legDeductions('return').length && !isCreditShell()) {\n <div class=\"sbd-cancel__refund-card-divider\"></div>\n <span class=\"sbd-cancel__badge sbd-cancel__badge--outline sbd-cancel__badge--muted\">Not Refunded - Deductions</span>\n @for (d of legDeductions('return'); track d.label) {\n <div class=\"sbd-cancel__refund-card-line\">\n <span>\n {{ d.label }}\n @if (d.badge) { <span class=\"sbd-cancel__tag\">{{ d.badge }}</span> }\n </span>\n <span class=\"sbd-cancel__refund-card-line--neg\">{{ fmtNeg(d.value) }}</span>\n </div>\n }\n }\n\n <div class=\"sbd-cancel__refund-card-divider sbd-cancel__refund-card-divider--dashed\"></div>\n <div class=\"sbd-cancel__refund-card-total\">\n <span>Return Total Refund</span>\n <strong>{{ fmt(legAdjustedData('return')?.totalRefundAmount) }}</strong>\n </div>\n\n @if (refundData()?.legInfo?.return?.isCreditShell && !hasGoldenGuaranteeGift()) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--credit\">\n <i class=\"pi pi-wallet\"></i>\n <span>\u2139\uFE0F The refund for the return flight has been issued as an Airline Credit Shell.</span>\n </div>\n }\n } @else {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--warning\">\n <i class=\"pi pi-ban\"></i>\n <span>This leg is not eligible for a refund.</span>\n </div>\n }\n </div>\n }\n </div>\n }\n }\n\n <!-- Instant refund notice -->\n @if (currentData()?.isRefundable) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--shield\">\n <i class=\"pi pi-shield\"></i>\n <span>\n @if (isNilRefund()) {\n We're sorry, as per airline policy, no refund applies because the cancellation fees equal or exceed your refundable fare amount.\n } @else {\n @if (isWalletOnlyRefund() || isLoyaltyOnlyRefund()) {\n Your refund is instant to the customer's almatar Wallet.\n } @else {\n Your refund is instant from our side, your bank may take up to 15 business days to reflect it.\n }\n }\n @if (!isNilRefund() && !isCreditShell()) {\n <strong>You can select refund option in the next step</strong>\n }\n </span>\n </div>\n }\n }\n\n </div>\n\n <!-- Footer -->\n <div class=\"sbd-cancel__footer\">\n @if (refundData()?.isRefundable) {\n <button pButton label=\"Keep booking\" styleClass=\"p-button-outlined p-button-secondary sbd-cancel__btn-keep\" (click)=\"closed.emit()\"></button>\n @if (currentData()?.isRefundable) {\n <button pButton\n styleClass=\"sbd-cancel__btn-primary\"\n [label]=\"isCreditShell() ? 'Continue Cancellation' : 'Select refund option'\"\n [disabled]=\"!canConfirm()\" (click)=\"requestCancel()\"></button>\n }\n } @else {\n <button pButton label=\"Close\" styleClass=\"p-button-outlined p-button-secondary\" (click)=\"closed.emit()\"></button>\n }\n </div>\n }\n\n <!-- \u2500\u2500 Result overlay: floating modal over the dimmed refund-method screen (scoped to this dialog, not the whole viewport) \u2500\u2500 -->\n @if (cancelResult()) {\n <div class=\"sbd-cancel__result-overlay\">\n <div class=\"sbd-cancel__result-backdrop\"></div>\n <div class=\"sbd-cancel__result-modal\">\n <div class=\"sbd-cancel__result-hero\">\n <span class=\"sbd-cancel__result-icon sbd-cancel__result-icon--green\">\n <i class=\"pi pi-check\"></i>\n </span>\n <h3 class=\"sbd-cancel__result-title\">{{ confirmIsNilRefund() ? 'Cancel Confirmed' : 'Refund Confirmed' }}</h3>\n </div>\n\n @for (r of confirmRefunds(); track r.label) {\n <div class=\"sbd-cancel__result-row\">\n <span class=\"sbd-cancel__method-icon\">\n <i class=\"pi\" [ngClass]=\"isWalletDestinationRow(r) ? 'pi-wallet' : 'pi-credit-card'\"></i>\n </span>\n <span class=\"sbd-cancel__result-body-text\">\n <span class=\"sbd-cancel__method-name\">{{ r.label }}</span>\n @if (r.subRows?.length) {\n <span class=\"sbd-cancel__result-sub-rows\">\n @for (sub of r.subRows; track sub.label) {\n <span class=\"sbd-cancel__result-sub-row\">\n <span>{{ sub.label }}</span>\n <strong>{{ fmtResult(sub.value) }}</strong>\n </span>\n }\n </span>\n }\n <span class=\"sbd-cancel__method-badge\"\n [ngClass]=\"isWalletDestinationRow(r) ? 'sbd-cancel__method-badge--instant' : 'sbd-cancel__method-badge--days'\">\n {{ isWalletDestinationRow(r) ? 'Ready Now' : (r.key === 'totalCardRefundAmount' ? confirmCardBadgeText() : 'Up to 15 business days, bank policy') }}\n </span>\n @if (r.hasLoyaltyNote) {\n <span class=\"sbd-cancel__notice sbd-cancel__notice--info\">\n <i class=\"pi pi-info-circle\"></i>\n <span>External loyalty points are refunded to your almatar Wallet as permanent points and ready to use in your next booking.</span>\n </span>\n }\n </span>\n <strong class=\"sbd-cancel__result-amount\">{{ fmtResult(r.value) }}</strong>\n </div>\n }\n\n @if (confirmCreditShellNote()) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--credit\">\n <i class=\"pi pi-wallet\"></i>\n <span>\u2139\uFE0F {{ confirmCreditShellNote() }}</span>\n </div>\n }\n\n <button pButton styleClass=\"sbd-cancel__btn-primary sbd-cancel__result-done\" label=\"Close\" (click)=\"closed.emit()\"></button>\n </div>\n </div>\n }\n\n </div>\n\n</p-dialog>\n\n<ng-template #madaLogo>\n <svg width=\"43\" height=\"15\" viewBox=\"0 0 43 15\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <g clip-path=\"url(#clip0_sbd_mada)\">\n <path d=\"M18.1894 8.6543H0V14.9933H18.1894V8.6543Z\" fill=\"#84B740\" />\n <path d=\"M18.1894 0H0V6.34463H18.1894V0Z\" fill=\"#259BD6\" />\n <path\n d=\"M36.3779 13.7034L36.2969 13.7203C36.016 13.7768 35.9134 13.7994 35.7082 13.7994C35.233 13.7994 34.6713 13.5452 34.6713 12.3475C34.6713 11.7316 34.7685 10.9124 35.6542 10.9124H35.6596C35.8108 10.9237 35.9836 10.9407 36.3077 11.0424L36.3779 11.065V13.7034ZM36.5237 7.73164L36.3779 7.75989V9.97458L36.2483 9.93503L36.2105 9.92373C36.0647 9.87853 35.7298 9.77684 35.4058 9.77684C33.6344 9.77684 33.2617 11.178 33.2617 12.3531C33.2617 13.9633 34.1258 14.8898 35.6326 14.8898C36.2699 14.8898 36.7397 14.822 37.215 14.6582C37.6524 14.5113 37.8091 14.3023 37.8091 13.8559V7.5C37.3878 7.5791 36.9504 7.65819 36.5237 7.73164Z\"\n fill=\"#747583\"\n />\n <path\n d=\"M41.651 13.7385L41.5754 13.7611L41.3053 13.8345C41.0515 13.9023 40.8247 13.9419 40.6519 13.9419C40.236 13.9419 39.9876 13.7272 39.9876 13.36C39.9876 13.1227 40.0902 12.7215 40.7707 12.7215H41.651V13.7385ZM41.0299 9.74414C40.4844 9.74414 39.9228 9.84584 39.2261 10.0718L38.7724 10.2131L38.9236 11.2865L39.3665 11.134C39.831 10.9758 40.4088 10.8741 40.8409 10.8741C41.0353 10.8741 41.6294 10.8741 41.6294 11.5464V11.8402H40.8031C39.2963 11.8402 38.5996 12.343 38.5996 13.4221C38.5996 14.343 39.2423 14.8967 40.3224 14.8967C40.6573 14.8967 41.1217 14.8289 41.5214 14.7272L41.543 14.7215L41.5646 14.7272L41.6996 14.7498C42.1208 14.8289 42.5583 14.908 42.9849 14.9927V11.4673C42.9849 10.3261 42.3261 9.74414 41.0299 9.74414Z\"\n fill=\"#747583\"\n />\n <path\n d=\"M31.1527 13.7385L31.0771 13.7611L30.807 13.8345C30.5532 13.9023 30.3318 13.9419 30.1536 13.9419C29.7377 13.9419 29.4893 13.7272 29.4893 13.36C29.4893 13.1227 29.5919 12.7215 30.267 12.7215H31.1473L31.1527 13.7385ZM30.537 9.74414C29.9861 9.74414 29.4299 9.84584 28.7332 10.0718L28.2795 10.2131L28.4308 11.2865L28.8736 11.134C29.3381 10.9758 29.9159 10.8741 30.348 10.8741C30.5424 10.8741 31.1365 10.8741 31.1365 11.5464V11.8402H30.3102C28.8034 11.8402 28.1013 12.343 28.1013 13.4221C28.1013 14.343 28.744 14.8967 29.8295 14.8967C30.1644 14.8967 30.6288 14.8289 31.0285 14.7272L31.0501 14.7215L31.0717 14.7272L31.2013 14.7498C31.6279 14.8289 32.06 14.908 32.4866 14.9984V11.473C32.492 10.3204 31.8332 9.74414 30.537 9.74414Z\"\n fill=\"#747583\"\n />\n <path\n d=\"M25.4701 9.7576C24.7842 9.7576 24.2172 9.99489 24.0065 10.0966L23.9525 10.1248L23.9039 10.0853C23.6123 9.86495 23.1856 9.75195 22.5916 9.75195C22.0677 9.75195 21.5762 9.83105 21.0416 9.99489C20.5825 10.1418 20.4043 10.3734 20.4043 10.8085V14.8367H21.8409V11.1135L21.9111 11.0909C22.2027 10.9892 22.3755 10.9723 22.543 10.9723C22.9588 10.9723 23.1694 11.2039 23.1694 11.6559V14.8423H24.5844V11.5938C24.5844 11.4017 24.5466 11.2887 24.5412 11.2661L24.4926 11.17L24.5898 11.1248C24.8058 11.0231 25.0435 10.9723 25.2919 10.9723C25.5781 10.9723 25.9184 11.0909 25.9184 11.6559V14.8423H27.3279V11.509C27.3279 10.3282 26.7231 9.7576 25.4701 9.7576Z\"\n fill=\"#747583\"\n />\n <path\n d=\"M40.5866 4.13559C40.3759 4.13559 40.0249 4.113 39.7495 4.0565L39.6684 4.03955V1.86441C39.6684 1.68362 39.636 1.57062 39.6306 1.55367L39.5874 1.46328L39.6793 1.42373C39.7009 1.41243 39.7225 1.40678 39.7495 1.39548L39.7657 1.38418C39.7981 1.37288 39.8305 1.36158 39.8629 1.35028C39.8791 1.34463 39.8899 1.33898 39.9007 1.33898C40.2193 1.24859 40.511 1.25989 40.6406 1.24859H40.646C41.5263 1.24859 41.6289 2.0678 41.6289 2.68362C41.6235 3.88136 41.0564 4.13559 40.5866 4.13559ZM40.5812 0C40.5704 0 40.5542 0 40.5434 0C39.7171 0 38.8692 0.237288 38.5667 0.700565C38.4047 0.926554 38.3129 1.20904 38.3075 1.54237V3.78531C38.3075 3.9774 38.2697 4.05085 38.2643 4.0678L38.2157 4.16384H35.6072V2.60452H35.6018C35.5694 0.960452 34.6404 0.0564972 33.2957 0.0564972H33.1391H31.9833C31.9293 0.457627 31.8861 0.740113 31.8321 1.14124H33.1391C33.8249 1.14124 34.1868 1.75141 34.1868 2.68927V4.25989L34.095 4.20904C34.0788 4.20339 33.9654 4.16384 33.7871 4.16384H31.5297C31.4865 4.46328 31.4325 4.85311 31.373 5.24294H38.3129C38.5505 5.19209 38.8259 5.14689 39.0636 5.10735C39.4146 5.28814 40.0681 5.38418 40.5164 5.38418C42.0231 5.38418 43.0007 4.32768 43.0007 2.70057C42.9953 1.0904 42.0501 0.0338983 40.5812 0Z\"\n fill=\"#747583\"\n />\n <path\n d=\"M28.4189 5.90821H28.4837C29.9905 5.90821 30.6925 5.38844 30.6925 4.10595C30.6925 3.18505 30.0499 2.45059 28.9697 2.45059H27.5818C27.1659 2.45059 26.9175 2.202 26.9175 1.78392C26.9175 1.50143 27.0201 1.15115 27.7006 1.15115H30.7357C30.8006 0.738723 30.833 0.478836 30.8924 0.0664062H27.7384C26.2694 0.0664062 25.5295 0.710474 25.5295 1.78392C25.5295 2.84607 26.1722 3.39974 27.2523 3.39974H28.6403C29.0561 3.39974 29.3046 3.74437 29.3046 4.10595C29.3046 4.34324 29.202 4.83477 28.5269 4.83477H28.2947L23.8499 4.82347H23.0398C22.3539 4.82347 21.8733 4.41669 21.8733 3.47319V2.82347C21.8733 1.84042 22.2459 1.23025 23.0398 1.23025H24.3576C24.417 0.812169 24.4548 0.546632 24.5088 0.151152H22.867H22.7104C21.3656 0.151152 20.4367 1.09466 20.4043 2.73872V2.80087V3.47319C20.4367 5.11725 21.3656 5.90256 22.7104 5.90256H22.867H24.0227L26.4314 5.90821H27.868H28.4189Z\"\n fill=\"#747583\"\n />\n </g>\n <defs>\n <clipPath id=\"clip0_sbd_mada\">\n <rect width=\"43\" height=\"15\" fill=\"white\" />\n </clipPath>\n </defs>\n </svg>\n</ng-template>\n", styles: [":host ::ng-deep .sbd-cancel-dialog-wrapper{border-radius:16px;overflow:hidden;box-shadow:0 24px 60px #0f172a38}:host ::ng-deep .sbd-cancel-dialog-wrapper .p-dialog-content{padding:0;border-radius:16px;overflow-y:auto;overflow-x:hidden}.sbd-cancel{font-family:inherit;border-radius:16px;overflow:visible;position:relative}.sbd-cancel__loading{padding:24px}.sbd-cancel__loading-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.sbd-cancel__loading-rows{display:flex;flex-direction:column;gap:10px;margin-top:20px}.sbd-cancel__loading-row{display:flex;justify-content:space-between}.sbd-cancel__are-you-sure{padding:36px 24px 28px;display:flex;flex-direction:column;align-items:center;gap:14px;text-align:center;border-top:1px solid #e2e8f0}.sbd-cancel__ays-icon{width:56px;height:56px;border-radius:50%;background:#fff7ed;border:2px solid #fed7aa;display:flex;align-items:center;justify-content:center}.sbd-cancel__ays-icon .pi{font-size:24px;color:#f97316}.sbd-cancel__ays-icon--warn{background:#fef2f2;border-color:#fecaca}.sbd-cancel__ays-icon--warn .pi{color:#ef4444}.sbd-cancel__ays-hint{font-size:13px;color:#64748b;min-height:20px;animation:sbd-hint-fade .35s ease-out}.sbd-cancel__ays-dots{display:flex;gap:6px}.sbd-cancel__ays-dots span{width:7px;height:7px;border-radius:50%;background:#f97316;opacity:.3;animation:sbd-dot-pulse 1.2s ease-in-out infinite}.sbd-cancel__ays-dots span:nth-child(2){animation-delay:.2s}.sbd-cancel__ays-dots span:nth-child(3){animation-delay:.4s}@keyframes sbd-hint-fade{0%{opacity:0;transform:translateY(5px)}to{opacity:1;transform:translateY(0)}}@keyframes sbd-dot-pulse{0%,to{opacity:.3;transform:scale(1)}50%{opacity:1;transform:scale(1.4)}}.sbd-cancel__header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #e2e8f0;background:#f8fafc}.sbd-cancel__header-left{display:flex;align-items:center;gap:10px}.sbd-cancel__header-icon{font-size:18px;color:#475569}.sbd-cancel__header-title{font-size:15px;font-weight:700;color:#1e2e4a}.sbd-cancel__header--stack{align-items:flex-start}.sbd-cancel__header-titles{display:flex;flex-direction:column;gap:2px}.sbd-cancel__header-sub{font-size:12.5px;font-weight:600;color:#94a3b8;font-variant-numeric:tabular-nums}.sbd-cancel__badge{font-size:11.5px;font-weight:700;border-radius:20px;padding:3px 12px;letter-spacing:.03em;width:max-content;text-transform:uppercase}.sbd-cancel__badge--green{background:#dcfce7;color:#166534}.sbd-cancel__badge--red{background:#fee2e2;color:#991b1b}.sbd-cancel__badge--orange{background:#fff7ed;color:#9a3412}.sbd-cancel__badge--outline{background:transparent;box-shadow:inset 0 0 0 1.5px currentColor;padding:4px 14px}.sbd-cancel__badge--outline.sbd-cancel__badge--green{color:#16a34a}.sbd-cancel__badge--outline.sbd-cancel__badge--red{color:#dc2626}.sbd-cancel__badge--muted{background:#f8fafc;color:#64748b;box-shadow:inset 0 0 0 1px #e2e8f0}.sbd-cancel__close-btn{width:30px;height:30px;border-radius:8px;border:none;background:transparent;color:#64748b;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background .15s,color .15s}.sbd-cancel__close-btn .pi{font-size:14px}.sbd-cancel__close-btn:hover{background:#e2e8f0;color:#1e2e4a}.sbd-cancel__close-btn--floating{position:absolute;top:12px;inset-inline-end:12px}.sbd-cancel__summary{background:#f8fafc;border:1px solid #e2e8f0;border-radius:12px;padding:14px 16px;display:flex;flex-direction:column;gap:10px}.sbd-cancel__summary-row{display:flex;align-items:center;justify-content:space-between;font-size:13.5px}.sbd-cancel__summary-row strong{font-size:15px;font-weight:800;color:#1e2e4a;font-variant-numeric:tabular-nums}.sbd-cancel__summary-row--refund .sbd-cancel__summary-label,.sbd-cancel__summary-row--refund strong{color:#16a34a}.sbd-cancel__summary-row--toggle{cursor:pointer}.sbd-cancel__summary-label{display:inline-flex;align-items:center;gap:8px;font-weight:600;color:#475569}.sbd-cancel__summary-label .pi{font-size:13px}.sbd-cancel__summary-value{display:flex;align-items:center;gap:4px}.sbd-cancel__summary-breakdown{display:flex;flex-direction:column;gap:6px;padding:2px 0 2px 22px}.sbd-cancel__summary-breakdown-row{display:flex;align-items:center;justify-content:space-between;font-size:12.5px;color:#64748b}.sbd-cancel__summary-breakdown-row span:last-child{font-variant-numeric:tabular-nums;font-weight:600;color:#475569}.sbd-cancel__section-title{font-size:14px;font-weight:700;color:#1e2e4a}.sbd-cancel__refund-card{border:1px solid #e2e8f0;border-radius:12px;padding:16px;display:flex;flex-direction:column;gap:4px}.sbd-cancel__refund-card-row{display:flex;align-items:center;justify-content:space-between;margin-top:10px;font-size:14px}.sbd-cancel__refund-card-row span{color:#334155;font-weight:600}.sbd-cancel__refund-card-row strong{color:#1e2e4a;font-weight:700;font-variant-numeric:tabular-nums}.sbd-cancel__refund-card-divider{border-top:1px solid #e2e8f0;margin:10px 0}.sbd-cancel__refund-card-divider--dashed{border-top-style:dashed}.sbd-cancel__refund-card-line{display:flex;align-items:center;justify-content:space-between;padding:5px 0;font-size:13px}.sbd-cancel__refund-card-line span:first-child{color:#64748b;display:inline-flex;align-items:center}.sbd-cancel__refund-card-line--neg{color:#64748b;font-weight:600;font-variant-numeric:tabular-nums}.sbd-cancel__refund-card-total{display:flex;align-items:center;justify-content:space-between}.sbd-cancel__refund-card-total span{font-size:14.5px;font-weight:700;color:#1e2e4a}.sbd-cancel__refund-card-total strong{font-size:17px;font-weight:800;color:#1e2e4a;font-variant-numeric:tabular-nums}.sbd-cancel__btn-keep{flex:1 1 0}.sbd-cancel__btn-primary{flex:1 1 0;background:#6d4aff!important;border-color:#6d4aff!important;color:#fff!important}.sbd-cancel__btn-primary:hover:not(:disabled){background:#5c3ce6!important;border-color:#5c3ce6!important}.sbd-cancel__btn-primary:disabled{opacity:.5}.sbd-cancel__paid{display:flex;align-items:center;justify-content:space-between;margin:0 -20px;padding:14px 20px;border-radius:0;background:#f1f0fb;border-top:1px solid #e7e5f7;border-bottom:1px solid #e7e5f7}.sbd-cancel__paid span{font-size:13.5px;font-weight:600;color:#4b4670}.sbd-cancel__paid strong{font-size:18px;font-weight:800;color:#2b2747;font-variant-numeric:tabular-nums;letter-spacing:.01em}.sbd-cancel__group{display:flex;flex-direction:column}.sbd-cancel__group-label{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.07em;margin-bottom:2px}.sbd-cancel__group-label--deduct{color:#e05656}.sbd-cancel__group-label--refund{color:#16a34a}.sbd-cancel__line{display:flex;justify-content:space-between;align-items:center;padding:11px 0;font-size:13.5px;border-bottom:1px dashed #e5e7eb}.sbd-cancel__line:last-child{border-bottom:none}.sbd-cancel__line-label{display:inline-flex;align-items:center;color:#334155}.sbd-cancel__line-val{font-weight:700;font-variant-numeric:tabular-nums}.sbd-cancel__line-val--neg{color:#dc2626}.sbd-cancel__line-val--pos{color:#16a34a}.sbd-cancel__tag{margin-left:8px;font-size:10px;font-weight:700;line-height:1;padding:3px 8px;border-radius:6px;background:#ede9fe;color:#6d28d9;letter-spacing:.02em}.sbd-cancel__total-deduct{display:flex;align-items:center;justify-content:space-between;padding:13px 16px;border-radius:10px;background:#fdf2f2;border:1px solid #f6c9c9}.sbd-cancel__total-deduct span{font-size:14px;font-weight:700;color:#b3344a}.sbd-cancel__total-deduct strong{font-size:15px;font-weight:800;color:#dc2626;font-variant-numeric:tabular-nums}.sbd-cancel__calc{display:flex;align-items:center;gap:12px;margin:2px 0;color:#94a3b8;font-size:13px;font-variant-numeric:tabular-nums}.sbd-cancel__calc:before,.sbd-cancel__calc:after{content:\"\";flex:1;height:1px;background:#e5e7eb}.sbd-cancel__refund-total{display:flex;align-items:center;justify-content:space-between}.sbd-cancel__refund-total span{font-size:16px;font-weight:800;color:#1e2e4a}.sbd-cancel__refund-total strong{font-size:26px;font-weight:800;color:#16a34a;font-variant-numeric:tabular-nums;letter-spacing:.01em}.sbd-cancel__formula{text-align:center;padding:14px 16px;border-radius:10px;background:#1f2438;color:#f1f5f9;font-family:SFMono-Regular,ui-monospace,Menlo,Consolas,monospace;font-size:14px;font-weight:600;letter-spacing:.04em;font-variant-numeric:tabular-nums}.sbd-cancel__discrepancy{display:flex;align-items:flex-start;gap:10px;padding:11px 13px;border-radius:8px;font-size:12.5px;line-height:1.45;background:#fffbeb;border:1px solid #fde68a;color:#92400e}.sbd-cancel__discrepancy i{flex-shrink:0;margin-top:1px;font-size:14px;color:#d97706}.sbd-cancel__body{padding:16px 20px;display:flex;flex-direction:column;gap:12px}.sbd-cancel__leg-card{width:100%;border-radius:12px;border:1px solid #e2e8f0;background:#fff;overflow:hidden}.sbd-cancel__leg-card-header{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:14px 16px;cursor:pointer}.sbd-cancel__leg-checkbox{display:flex;align-items:center;gap:10px;font-size:13.5px;font-weight:700;color:#1e2e4a;cursor:pointer}.sbd-cancel__leg-checkbox input[type=checkbox]{width:17px;height:17px;accent-color:#6d4aff;cursor:pointer}.sbd-cancel__leg-chevron{flex-shrink:0;width:26px;height:26px;display:flex;align-items:center;justify-content:center;border:none;background:transparent;color:#64748b;cursor:pointer;transition:transform .15s}.sbd-cancel__leg-chevron i{font-size:13px}.sbd-cancel__leg-chevron--collapsed{transform:rotate(180deg)}.sbd-cancel__leg-card-body{display:flex;flex-direction:column;gap:4px;padding:0 16px 16px}.sbd-cancel__leg-card-body--excluded{opacity:.5}.sbd-cancel__notice{display:flex;align-items:flex-start;gap:10px;padding:10px 12px;border-radius:8px;font-size:12.5px}.sbd-cancel__notice i{flex-shrink:0;margin-top:1px;font-size:14px}.sbd-cancel__notice p{margin:2px 0 0;font-size:12px;opacity:.8}.sbd-cancel__notice strong{display:block;font-weight:700}.sbd-cancel__notice--warning{background:#fff7ed;border:1px solid #fed7aa;color:#9a3412}.sbd-cancel__notice--warning i{color:#f97316}.sbd-cancel__notice--info{background:#eff6ff;border:1px solid #bfdbfe;color:#1e40af}.sbd-cancel__notice--info i{color:#3b82f6}.sbd-cancel__notice--neutral{background:#f7f7f8;border:1px solid #e5e7eb;color:#111827}.sbd-cancel__notice--neutral i{color:#111827}.sbd-cancel__notice--tip{background:#f0fdf4;border:1px solid #bbf7d0;color:#14532d}.sbd-cancel__notice--tip i{color:#22c55e}.sbd-cancel__notice--success{background:#f0fdf4;border:1px solid #bbf7d0;color:#15803c}.sbd-cancel__notice--success i{color:#16a34a}.sbd-cancel__notice--credit{background:#faf5ff;border:1px solid #e9d5ff;color:#5b21b6;font-size:12px}.sbd-cancel__notice--credit i{color:#a855f7}.sbd-cancel__notice--shield{background:#eff6ff;border:1px solid #bfdbfe;color:#1e40af}.sbd-cancel__notice--shield strong{display:inline;font-weight:700}.sbd-cancel__notice--shield i{color:#3b82f6}.sbd-cancel__source-selector{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:4px 0}.sbd-cancel__source-label{font-size:11px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap;margin-right:2px}.sbd-cancel__source-chip{font-size:12px;font-weight:600;padding:4px 14px;border-radius:20px;border:1.5px solid #e2e8f0;background:#f8fafc;color:#475569;cursor:pointer;transition:border-color .15s,color .15s,background .15s;display:inline-flex;align-items:center}.sbd-cancel__source-chip:hover{border-color:#94a3b8;color:#475569;background:#f1f5f9}.sbd-cancel__source-chip--active{background:#eff6ff;color:#1d4ed8;border-color:#93c5fd}.sbd-cancel__source-chip--active:hover{background:#dbeafe;color:#1d4ed8;border-color:#93c5fd}.sbd-cancel__source-chip--no-refund{border-color:#fecaca;color:#b91c1c;background:#fff5f5}.sbd-cancel__source-chip--no-refund:hover{border-color:#fca5a5;background:#fee2e2;color:#b91c1c}.sbd-cancel__source-chip--active.sbd-cancel__source-chip--no-refund{background:#fee2e2;color:#991b1b;border-color:#fca5a5}.sbd-cancel__source-chip--active.sbd-cancel__source-chip--no-refund:hover{background:#fecaca}.sbd-cancel__footer{display:flex;justify-content:flex-end;gap:10px;align-items:stretch;padding:14px 20px;border-top:1px solid #e2e8f0;background:#fff}.sbd-cancel__btn-danger-pill{flex:1 1 0;border-radius:999px!important;background:#fee2e2!important;border-color:#fee2e2!important;color:#dc2626!important;font-weight:700!important}.sbd-cancel__btn-danger-pill:hover:not(:disabled){background:#fecaca!important;border-color:#fecaca!important}.sbd-cancel__btn-danger-pill:disabled{opacity:.5}.sbd-cancel__method-header{display:flex;align-items:flex-start;gap:12px;padding:20px 20px 0}.sbd-cancel__method-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-cancel__method-close .pi{font-size:13px}.sbd-cancel__method-close:hover{background:#e2e8f0}.sbd-cancel__method-header-text{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px;text-align:center;margin-right:32px}.sbd-cancel__method-title{font-size:19px;font-weight:800;color:#1e2e4a;margin:0}.sbd-cancel__method-sub{font-size:12.5px;color:#94a3b8}.sbd-cancel__method-body{padding:16px 20px 20px;display:flex;flex-direction:column;gap:14px}.sbd-cancel__select-leg-body{padding:16px 20px 20px;display:flex;flex-direction:column;gap:12px}.sbd-cancel__leg-select-card{position:relative;border:1.5px solid #e2e8f0;border-radius:16px;padding:16px 16px 16px 48px;transition:border-color .15s}.sbd-cancel__leg-select-card--selected:not(.sbd-cancel__leg-select-card--frozen){border-color:#6d4aff}.sbd-cancel__leg-select-card--disabled{opacity:.7}.sbd-cancel__leg-select-card--frozen{opacity:.6;border-color:#e2e8f0}.sbd-cancel__leg-select-lock{position:absolute;top:16px;left:16px;width:22px;height:22px;display:flex;align-items:center;justify-content:center;font-size:14px;color:#94a3b8}.sbd-cancel__single-leg-note{display:flex;align-items:flex-start;gap:10px;padding:12px 14px;border-radius:12px;background:#eaf2ff;border:1px solid #b9d6ff;color:#1a5fd6;font-size:13px;line-height:1.5}.sbd-cancel__single-leg-note i{flex-shrink:0;margin-top:1px}.sbd-cancel__all-non-refundable-note{display:flex;align-items:flex-start;gap:10px;padding:12px 14px;border-radius:12px;background:#fdf3e3;border:1px solid #f3d9a4;color:#8a5a1e;font-size:13px;line-height:1.5}.sbd-cancel__all-non-refundable-note i{flex-shrink:0;margin-top:1px;color:#b98a3e}.sbd-cancel__leg-select-checkbox{position:absolute;top:16px;left:16px;width:22px;height:22px;cursor:pointer}.sbd-cancel__leg-select-checkbox input{position:absolute;inset:0;opacity:0;margin:0;cursor:pointer}.sbd-cancel__leg-select-checkbox input:disabled{cursor:not-allowed}.sbd-cancel__leg-select-icon{display:flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:6px;border:1.5px solid #cbd5e1;background:#fff;color:#fff;font-size:12px}input:checked+.sbd-cancel__leg-select-icon{background:#6d4aff;border-color:#6d4aff}input:disabled+.sbd-cancel__leg-select-icon{background:#f1f5f9;border-color:#e2e8f0}.sbd-cancel__leg-select-content{display:flex;flex-direction:column;gap:8px}.sbd-cancel__leg-select-top{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}.sbd-cancel__leg-select-name{font-size:15px;font-weight:700;color:#1e2e4a}.sbd-cancel__leg-select-date{font-size:13px;color:#64748b;margin-top:2px}.sbd-cancel__leg-select-times{display:flex;align-items:center;gap:8px}.sbd-cancel__leg-select-time{font-size:18px;font-weight:700;color:#1e2e4a;font-variant-numeric:tabular-nums}.sbd-cancel__leg-select-line{flex:1;position:relative;display:flex;flex-direction:column;align-items:center;gap:2px;padding:0 16px 0 8px}.sbd-cancel__leg-select-line:before{content:\"\";position:absolute;left:0;right:14px;top:50%;height:2px;background:#cbd5e1;z-index:1}.sbd-cancel__leg-select-line-stops{font-size:12px;color:#64748b}.sbd-cancel__leg-select-line-dur{font-size:11px;color:#94a3b8}.sbd-cancel__leg-select-line-arrow{position:absolute;right:4px;top:51%;transform:translateY(-50%);font-size:11px;color:#64748b}.sbd-cancel__leg-select-footer{display:flex;align-items:center;gap:8px}.sbd-cancel__leg-select-airline{font-size:12.5px;color:#64748b}.sbd-cancel__leg-select-operated-by{font-size:12px;color:#94a3b8}.sbd-cancel__leg-select-chevron{font-size:12px;color:#94a3b8;margin-inline-start:auto}.sbd-cancel__leg-select-hint{text-align:center;padding:8px 20px 0;font-size:13px;color:#94a3b8}.sbd-cancel__leg-select-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-cancel__leg-select-notice i{flex-shrink:0;margin-top:1px;color:#b98a3e}.sbd-cancel__method-amount{display:flex;align-items:center;justify-content:space-between}.sbd-cancel__method-amount span{font-size:14px;color:#1e2e4a}.sbd-cancel__method-amount strong{font-size:22px;font-weight:800;color:#1e2e4a;font-variant-numeric:tabular-nums}.sbd-cancel__method-option{display:flex;align-items:flex-start;gap:12px;width:100%;text-align:left;padding:16px;border-radius:16px;border:1.5px solid #e2e8f0;background:#fff;cursor:pointer;transition:border-color .15s,box-shadow .15s}.sbd-cancel__method-option--active{border-color:#6d4aff;box-shadow:0 0 0 1px #6d4aff}.sbd-cancel__method-option:disabled{opacity:.5;cursor:not-allowed}.sbd-cancel__method-icon{flex-shrink:0;width:44px;height:44px;border-radius:10px;display:flex;align-items:center;justify-content:center;border:1px solid #e2e8f0;color:#1e2e4a}.sbd-cancel__method-icon .pi{font-size:16px}.sbd-cancel__method-body-text{flex:1;display:flex;flex-direction:column;align-items:flex-start;gap:4px}.sbd-cancel__method-name{font-size:16px;font-weight:700;color:#1e2e4a;display:inline-flex;align-items:center;gap:6px}.sbd-cancel__method-desc{font-size:13px;color:#64748b}.sbd-cancel__method-badge{display:inline-flex;align-items:center;gap:5px;margin-top:4px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.03em;padding:4px 10px;border-radius:20px}.sbd-cancel__method-badge svg{flex-shrink:0}.sbd-cancel__method-badge--instant{background:#dcfce7;color:#166534}.sbd-cancel__method-badge--days,.sbd-cancel__method-badge--loyalty{background:#eff6ff;color:#1e40af}.sbd-cancel__option-end{flex-shrink:0;display:flex;align-items:center;gap:8px}.sbd-cancel__method-radio{flex-shrink:0;width:18px;height:18px;margin-top:2px;border-radius:50%;border:2px solid #7c3aed;position:relative}.sbd-cancel__method-option--active .sbd-cancel__method-radio{border-color:#6d4aff}.sbd-cancel__method-option--active .sbd-cancel__method-radio:after{content:\"\";position:absolute;inset:3px;border-radius:50%;background:#6d4aff}.sbd-cancel__method-option--split{flex-wrap:wrap}.sbd-cancel__method-split-total{flex-shrink:0;font-size:16px;font-weight:700;color:#1e2e4a;font-variant-numeric:tabular-nums}.sbd-cancel__method-split-breakdown{flex-basis:100%;display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;margin-top:12px;padding-top:12px;border-top:1px solid #e2e8f0}.sbd-cancel__method-split-row{flex:1 1 auto;display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%;font-size:14px;color:#1e2e4a}.sbd-cancel__method-split-row strong{font-weight:700;font-variant-numeric:tabular-nums}.sbd-cancel__method-split-label{color:#64748b;display:inline-flex;align-items:center;gap:6px}.sbd-cancel__mada-logo{display:inline-flex;align-items:center;flex-shrink:0}.sbd-cancel__mada-logo svg{display:block}.sbd-cancel__result-hero{display:flex;flex-direction:column;align-items:center;gap:18px;padding:28px 0 8px}.sbd-cancel__result-icon{width:84px;height:84px;border-radius:50%;display:flex;align-items:center;justify-content:center}.sbd-cancel__result-icon .pi{font-size:30px;color:#fff}.sbd-cancel__result-icon--green{background:#4ade80}.sbd-cancel__result-icon--orange{background:#fb923c}.sbd-cancel__result-title{font-size:19px;font-weight:800;color:#1e2e4a;margin:0;text-align:center}.sbd-cancel__result-row{display:flex;align-items:flex-start;gap:12px;width:100%;padding:12px 14px;border-radius:14px;background:#f8fafc}.sbd-cancel__result-body-text{flex:1;min-width:0;display:flex;flex-direction:column;align-items:flex-start;gap:5px}.sbd-cancel__result-body-text .sbd-cancel__method-badge{text-transform:none;letter-spacing:normal;line-height:1.35;white-space:normal}.sbd-cancel__result-sub-rows{display:flex;flex-direction:column;gap:2px;width:100%}.sbd-cancel__result-sub-row{display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;font-size:12.5px;color:#64748b}.sbd-cancel__result-sub-row strong{font-weight:700;color:#64748b;font-variant-numeric:tabular-nums}.sbd-cancel__result-amount{font-size:16px;font-weight:800;color:#1e2e4a;font-variant-numeric:tabular-nums;white-space:nowrap}.sbd-cancel__result-overlay{position:absolute;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;padding:24px}.sbd-cancel__result-backdrop{position:absolute;inset:0;background:#0000008c}.sbd-cancel__result-modal{position:relative;width:100%;max-width:400px;max-height:90%;overflow-y:auto;display:flex;flex-direction:column;gap:16px;padding:24px 20px;border-radius:20px;background:#fff;box-shadow:0 24px 60px #00000059}.sbd-cancel__result-done{width:100%}\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: i3$1.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "label", "icon", "loading"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
2579
+ }
2580
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdCancelDialogComponent, decorators: [{
2581
+ type: Component,
2582
+ args: [{ selector: 'app-sbd-cancel-dialog', standalone: true, imports: [DialogModule, ButtonModule, SkeletonModule, NgTemplateOutlet, NgClass], 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-cancel-dialog-wrapper\">\n\n <div class=\"sbd-cancel\">\n\n <!-- Loading refund details \u2014 shows cycling hints -->\n @if (loading()) {\n <div class=\"sbd-cancel__are-you-sure\">\n <button type=\"button\" class=\"sbd-cancel__close-btn sbd-cancel__close-btn--floating\" (click)=\"closed.emit()\" aria-label=\"Close\">\n <i class=\"pi pi-times\"></i>\n </button>\n <div class=\"sbd-cancel__ays-icon\">\n <i class=\"pi pi-spin pi-spinner\"></i>\n </div>\n @for (h of [cancelHint()]; track h) {\n <div class=\"sbd-cancel__ays-hint\">{{ h }}</div>\n }\n <div class=\"sbd-cancel__ays-dots\">\n <span></span><span></span><span></span>\n </div>\n </div>\n }\n\n <!-- Select leg \u2014 shown once the refund preview has fully loaded, before the\n refund breakdown, for round-trip bookings the backend hasn't already\n restricted to a single leg. Same card design as almatar-b2c's\n select-leg screen (checkbox overlay, refundable badge, disclaimer). -->\n @else if (step() === 'select-leg') {\n <div class=\"sbd-cancel__method-header\">\n <button type=\"button\" class=\"sbd-cancel__method-close\" (click)=\"closed.emit()\" aria-label=\"Close\">\n <i class=\"pi pi-times\"></i>\n </button>\n <div class=\"sbd-cancel__method-header-text\">\n <h3 class=\"sbd-cancel__method-title\">Select Leg to Cancel</h3>\n <span class=\"sbd-cancel__method-sub\">Booking ID #{{ bookingId() }}</span>\n </div>\n </div>\n\n <div class=\"sbd-cancel__select-leg-body\">\n @if (forcedLegType(); as forced) {\n <div class=\"sbd-cancel__single-leg-note\">\n <i class=\"pi pi-info-circle\"></i>\n <span>This airline doesn't allow cancelling a single flight \u2014 {{ itineraryLeg(forced)?.from }} to {{ itineraryLeg(forced)?.to }} will be included automatically.</span>\n </div>\n }\n @if (allLegsNonRefundable()) {\n <div class=\"sbd-cancel__all-non-refundable-note\">\n <i class=\"pi pi-exclamation-triangle\"></i>\n <span>Sorry, we're unable to provide a refund for this booking, as both flights are non-refundable based on airline rules.</span>\n </div>\n }\n <!-- Onward -->\n <div class=\"sbd-cancel__leg-select-card\"\n [ngClass]=\"{\n 'sbd-cancel__leg-select-card--selected': onwardChecked() && onwardRefundable(),\n 'sbd-cancel__leg-select-card--disabled': !onwardRefundable(),\n 'sbd-cancel__leg-select-card--frozen': forcedLegType() === 'onward'\n }\">\n @if (forcedLegType() !== 'onward') {\n <label class=\"sbd-cancel__leg-select-checkbox\">\n <input type=\"checkbox\" [checked]=\"onwardChecked()\" [disabled]=\"!onwardRefundable()\"\n (change)=\"toggleLeg('onward')\" />\n <span class=\"sbd-cancel__leg-select-icon\"><i class=\"pi pi-check\"></i></span>\n </label>\n } @else {\n <i class=\"pi pi-lock sbd-cancel__leg-select-lock\"></i>\n }\n <div class=\"sbd-cancel__leg-select-content\">\n @if (itineraryLeg('onward'); as leg) {\n <div class=\"sbd-cancel__leg-select-top\">\n <div>\n <span class=\"sbd-cancel__leg-select-name\">{{ leg.from }} to {{ leg.to }}</span>\n <div class=\"sbd-cancel__leg-select-date\">{{ fmtShortDate(leg.depTime) }}</div>\n </div>\n <span class=\"sbd-cancel__badge sbd-cancel__badge--outline\"\n [ngClass]=\"onwardRefundable() && forcedLegType() !== 'onward' ? 'sbd-cancel__badge--green' : 'sbd-cancel__badge--muted'\">\n {{ onwardRefundable() ? 'Refundable' : 'Non-Refundable' }}\n </span>\n </div>\n <div class=\"sbd-cancel__leg-select-times\">\n <span class=\"sbd-cancel__leg-select-time\">{{ fmtTime(leg.depTime) }}</span>\n <span class=\"sbd-cancel__leg-select-line\">\n <span class=\"sbd-cancel__leg-select-line-stops\">{{ leg.stops.length > 0 ? (leg.stops.length + ' stop' + (leg.stops.length > 1 ? 's' : '')) : 'Direct' }}</span>\n @if (fmtMins(leg.totalMinutes)) { <span class=\"sbd-cancel__leg-select-line-dur\">{{ fmtMins(leg.totalMinutes) }}</span> }\n <i class=\"pi pi-arrow-right sbd-cancel__leg-select-line-arrow\"></i>\n </span>\n <span class=\"sbd-cancel__leg-select-time\">{{ fmtTime(leg.arrTime) }}</span>\n </div>\n @if (leg.airlineName || leg.cabin) {\n <span class=\"sbd-cancel__leg-select-airline\">\n {{ leg.airlineName }}{{ leg.airlineName && leg.cabin ? ' \u00B7 ' : '' }}{{ leg.cabin }}\n </span>\n }\n <div class=\"sbd-cancel__leg-select-footer\">\n @if (leg.operatedBy.length) {\n <span class=\"sbd-cancel__leg-select-operated-by\">Operated by {{ leg.operatedBy.join(', ') }}</span>\n }\n <!-- <i class=\"pi pi-chevron-down sbd-cancel__leg-select-chevron\"></i> -->\n </div>\n } @else {\n <div class=\"sbd-cancel__leg-select-top\">\n <span class=\"sbd-cancel__leg-select-name\">Onward</span>\n <span class=\"sbd-cancel__badge sbd-cancel__badge--outline\"\n [ngClass]=\"onwardRefundable() && forcedLegType() !== 'onward' ? 'sbd-cancel__badge--green' : 'sbd-cancel__badge--muted'\">\n {{ onwardRefundable() ? 'Refundable' : 'Non-Refundable' }}\n </span>\n </div>\n }\n @if (!onwardRefundable()) {\n <div class=\"sbd-cancel__leg-select-notice\">\n <i class=\"pi pi-info-circle\"></i>\n <span>This flight type doesn't allow refunds on cancellation, based on the airline's fare rules.</span>\n </div>\n }\n </div>\n </div>\n\n <!-- Return -->\n <div class=\"sbd-cancel__leg-select-card\"\n [ngClass]=\"{\n 'sbd-cancel__leg-select-card--selected': returnChecked() && returnRefundable(),\n 'sbd-cancel__leg-select-card--disabled': !returnRefundable(),\n 'sbd-cancel__leg-select-card--frozen': forcedLegType() === 'return'\n }\">\n @if (forcedLegType() !== 'return') {\n <label class=\"sbd-cancel__leg-select-checkbox\">\n <input type=\"checkbox\" [checked]=\"returnChecked()\" [disabled]=\"!returnRefundable()\"\n (change)=\"toggleLeg('return')\" />\n <span class=\"sbd-cancel__leg-select-icon\"><i class=\"pi pi-check\"></i></span>\n </label>\n } @else {\n <i class=\"pi pi-lock sbd-cancel__leg-select-lock\"></i>\n }\n <div class=\"sbd-cancel__leg-select-content\">\n @if (itineraryLeg('return'); as leg) {\n <div class=\"sbd-cancel__leg-select-top\">\n <div>\n <span class=\"sbd-cancel__leg-select-name\">{{ leg.from }} to {{ leg.to }}</span>\n <div class=\"sbd-cancel__leg-select-date\">{{ fmtShortDate(leg.depTime) }}</div>\n </div>\n <span class=\"sbd-cancel__badge sbd-cancel__badge--outline\"\n [ngClass]=\"returnRefundable() && forcedLegType() !== 'return' ? 'sbd-cancel__badge--green' : 'sbd-cancel__badge--muted'\">\n {{ returnRefundable() ? 'Refundable' : 'Non-Refundable' }}\n </span>\n </div>\n <div class=\"sbd-cancel__leg-select-times\">\n <span class=\"sbd-cancel__leg-select-time\">{{ fmtTime(leg.depTime) }}</span>\n <span class=\"sbd-cancel__leg-select-line\">\n <span class=\"sbd-cancel__leg-select-line-stops\">{{ leg.stops.length > 0 ? (leg.stops.length + ' stop' + (leg.stops.length > 1 ? 's' : '')) : 'Direct' }}</span>\n @if (fmtMins(leg.totalMinutes)) { <span class=\"sbd-cancel__leg-select-line-dur\">{{ fmtMins(leg.totalMinutes) }}</span> }\n <i class=\"pi pi-arrow-right sbd-cancel__leg-select-line-arrow\"></i>\n </span>\n <span class=\"sbd-cancel__leg-select-time\">{{ fmtTime(leg.arrTime) }}</span>\n </div>\n @if (leg.airlineName || leg.cabin) {\n <span class=\"sbd-cancel__leg-select-airline\">\n {{ leg.airlineName }}{{ leg.airlineName && leg.cabin ? ' \u00B7 ' : '' }}{{ leg.cabin }}\n </span>\n }\n <div class=\"sbd-cancel__leg-select-footer\">\n @if (leg.operatedBy.length) {\n <span class=\"sbd-cancel__leg-select-operated-by\">Operated by {{ leg.operatedBy.join(', ') }}</span>\n }\n <!-- <i class=\"pi pi-chevron-down sbd-cancel__leg-select-chevron\"></i> -->\n </div>\n } @else {\n <div class=\"sbd-cancel__leg-select-top\">\n <span class=\"sbd-cancel__leg-select-name\">Return</span>\n <span class=\"sbd-cancel__badge sbd-cancel__badge--outline\"\n [ngClass]=\"returnRefundable() && forcedLegType() !== 'return' ? 'sbd-cancel__badge--green' : 'sbd-cancel__badge--muted'\">\n {{ returnRefundable() ? 'Refundable' : 'Non-Refundable' }}\n </span>\n </div>\n }\n @if (!returnRefundable()) {\n <div class=\"sbd-cancel__leg-select-notice\">\n <i class=\"pi pi-info-circle\"></i>\n <span>This flight type doesn't allow refunds on cancellation, based on the airline's fare rules.</span>\n </div>\n }\n </div>\n </div>\n </div>\n\n @if (!allLegsNonRefundable() && !onwardChecked() && !returnChecked()) {\n <div class=\"sbd-cancel__leg-select-hint\">Select at least one flight to continue</div>\n }\n\n <div class=\"sbd-cancel__footer\">\n <button pButton\n styleClass=\"sbd-cancel__btn-primary\"\n label=\"Continue\"\n [disabled]=\"allLegsNonRefundable() || (!onwardChecked() && !returnChecked())\"\n (click)=\"step.set('details')\"></button>\n </div>\n }\n\n <!-- Refund method \u2014 replaces the old are-you-sure + radio picker.\n Stays mounted (dimmed) behind the result overlay once confirmed. -->\n @else if (confirming() || cancelResult()) {\n <div class=\"sbd-cancel__method-header\">\n <button type=\"button\" class=\"sbd-cancel__method-close\" (click)=\"confirming.set(false)\" aria-label=\"Close\">\n <i class=\"pi pi-times\"></i>\n </button>\n <div class=\"sbd-cancel__method-header-text\">\n <h3 class=\"sbd-cancel__method-title\">Refund Method</h3>\n <span class=\"sbd-cancel__method-sub\">Booking ID #{{ bookingId() }}</span>\n </div>\n </div>\n\n <div class=\"sbd-cancel__method-body\">\n <div class=\"sbd-cancel__method-amount\">\n <span>You will get back</span>\n <strong>{{ fmt(adjustedData()?.totalRefundAmount) }}</strong>\n </div>\n\n @if (isCreditShell()) {\n <!-- Credit-shell refunds have exactly one destination \u2014 the airline\n credit shell \u2014 so show it as a read-only card, not a picker. -->\n <div class=\"sbd-cancel__method-option sbd-cancel__method-option--active\">\n <span class=\"sbd-cancel__method-icon\"><i class=\"pi pi-wallet\"></i></span>\n <span class=\"sbd-cancel__method-body-text\">\n <span class=\"sbd-cancel__method-name\">{{ creditShellSupplierName() }} website credit</span>\n <span class=\"sbd-cancel__method-desc\">Your refund will be issued instantly by {{ creditShellSupplierName() }}.</span>\n <span class=\"sbd-cancel__method-badge sbd-cancel__method-badge--instant\">\n <svg width=\"12\" height=\"9\" viewBox=\"0 0 12 9\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"M7.31818 0C8.42727 0 9.49093 0.440583 10.2752 1.22483C11.0594 2.00907 11.5 3.07273 11.5 4.18182C11.5 5.2909 11.0594 6.35457 10.2752 7.13881C9.49093 7.92305 8.42727 8.36364 7.31818 8.36364C6.20909 8.36364 5.14543 7.92305 4.36119 7.13881C3.57695 6.35457 3.13636 5.2909 3.13636 4.18182C3.13636 3.07273 3.57695 2.00907 4.36119 1.22483C5.14543 0.440583 6.20909 0 7.31818 0ZM7.31818 1.04545C6.48637 1.04545 5.68862 1.37589 5.10044 1.96407C4.51226 2.55226 4.18182 3.35 4.18182 4.18182C4.18182 5.01363 4.51226 5.81138 5.10044 6.39956C5.68862 6.98774 6.48637 7.31818 7.31818 7.31818C8.15 7.31818 8.94774 6.98774 9.53593 6.39956C10.1241 5.81138 10.4545 5.01363 10.4545 4.18182C10.4545 3.35 10.1241 2.55226 9.53593 1.96407C8.94774 1.37589 8.15 1.04545 7.31818 1.04545ZM6.79545 2.09091H7.57955V4.06682L8.7975 5.28477L8.24341 5.83886L6.79545 4.39091V2.09091ZM0.522727 7.31818C0.384091 7.31818 0.251134 7.26311 0.153103 7.16508C0.0550729 7.06705 0 6.93409 0 6.79545C0 6.65682 0.0550729 6.52386 0.153103 6.42583C0.251134 6.3278 0.384091 6.27273 0.522727 6.27273H2.52477C2.68682 6.64386 2.89591 6.99409 3.13636 7.31818H0.522727ZM1.04545 4.70455C0.906819 4.70455 0.773861 4.64947 0.67583 4.55144C0.5778 4.45341 0.522727 4.32045 0.522727 4.18182C0.522727 4.04318 0.5778 3.91022 0.67583 3.81219C0.773861 3.71416 0.906819 3.65909 1.04545 3.65909H2.11705L2.09091 4.18182L2.11705 4.70455H1.04545ZM1.56818 2.09091C1.42955 2.09091 1.29659 2.03584 1.19856 1.93781C1.10053 1.83978 1.04545 1.70682 1.04545 1.56818C1.04545 1.42955 1.10053 1.29659 1.19856 1.19856C1.29659 1.10053 1.42955 1.04545 1.56818 1.04545H3.13636C2.89591 1.36955 2.68682 1.71977 2.52477 2.09091H1.56818Z\"\n fill=\"#15803C\"\n />\n </svg>\n Instant\n </span>\n </span>\n </div>\n\n <div class=\"sbd-cancel__notice sbd-cancel__notice--credit\">\n <i class=\"pi pi-info-circle\"></i>\n <span>\n Your refund will be issued as airline credit under the same PNR.\n It can be used only for future booking on {{ creditShellSupplierName() }} website.\n </span>\n </div>\n\n <!-- Refundable ancillaries (baggage, seats, \u2026) can independently be\n flagged credit-shell or not \u2014 each renders as its own card,\n matching either the credit-shell style above or the normal\n wallet style, both with the same nested note when credit-shell. -->\n @for (item of creditShellAncillaryItems(); track item.name) {\n <div class=\"sbd-cancel__method-option sbd-cancel__method-option--active\">\n <span class=\"sbd-cancel__method-icon\"><i class=\"pi pi-wallet\"></i></span>\n <span class=\"sbd-cancel__method-body-text\">\n <span class=\"sbd-cancel__method-name\">\n {{ item.isCreditShell ? (creditShellSupplierName() + ' website credit') : 'Almatar Wallet' }}\n \u2014 {{ item.name }}\n </span>\n <span class=\"sbd-cancel__method-desc\">\n @if (item.isCreditShell) {\n Your refund will be issued instantly by {{ creditShellSupplierName() }}.\n } @else {\n Usable for future bookings\n }\n </span>\n <span class=\"sbd-cancel__method-badge sbd-cancel__method-badge--instant\">\n <svg width=\"12\" height=\"9\" viewBox=\"0 0 12 9\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"M7.31818 0C8.42727 0 9.49093 0.440583 10.2752 1.22483C11.0594 2.00907 11.5 3.07273 11.5 4.18182C11.5 5.2909 11.0594 6.35457 10.2752 7.13881C9.49093 7.92305 8.42727 8.36364 7.31818 8.36364C6.20909 8.36364 5.14543 7.92305 4.36119 7.13881C3.57695 6.35457 3.13636 5.2909 3.13636 4.18182C3.13636 3.07273 3.57695 2.00907 4.36119 1.22483C5.14543 0.440583 6.20909 0 7.31818 0ZM7.31818 1.04545C6.48637 1.04545 5.68862 1.37589 5.10044 1.96407C4.51226 2.55226 4.18182 3.35 4.18182 4.18182C4.18182 5.01363 4.51226 5.81138 5.10044 6.39956C5.68862 6.98774 6.48637 7.31818 7.31818 7.31818C8.15 7.31818 8.94774 6.98774 9.53593 6.39956C10.1241 5.81138 10.4545 5.01363 10.4545 4.18182C10.4545 3.35 10.1241 2.55226 9.53593 1.96407C8.94774 1.37589 8.15 1.04545 7.31818 1.04545ZM6.79545 2.09091H7.57955V4.06682L8.7975 5.28477L8.24341 5.83886L6.79545 4.39091V2.09091ZM0.522727 7.31818C0.384091 7.31818 0.251134 7.26311 0.153103 7.16508C0.0550729 7.06705 0 6.93409 0 6.79545C0 6.65682 0.0550729 6.52386 0.153103 6.42583C0.251134 6.3278 0.384091 6.27273 0.522727 6.27273H2.52477C2.68682 6.64386 2.89591 6.99409 3.13636 7.31818H0.522727ZM1.04545 4.70455C0.906819 4.70455 0.773861 4.64947 0.67583 4.55144C0.5778 4.45341 0.522727 4.32045 0.522727 4.18182C0.522727 4.04318 0.5778 3.91022 0.67583 3.81219C0.773861 3.71416 0.906819 3.65909 1.04545 3.65909H2.11705L2.09091 4.18182L2.11705 4.70455H1.04545ZM1.56818 2.09091C1.42955 2.09091 1.29659 2.03584 1.19856 1.93781C1.10053 1.83978 1.04545 1.70682 1.04545 1.56818C1.04545 1.42955 1.10053 1.29659 1.19856 1.19856C1.29659 1.10053 1.42955 1.04545 1.56818 1.04545H3.13636C2.89591 1.36955 2.68682 1.71977 2.52477 2.09091H1.56818Z\"\n fill=\"#15803C\"\n />\n </svg>\n Instant\n </span>\n @if (item.isCreditShell) {\n <span class=\"sbd-cancel__notice sbd-cancel__notice--info\">\n <i class=\"pi pi-info-circle\"></i>\n <span>Your refund will be issued as airline credit under the same PNR. It can be used only for future booking on {{ creditShellSupplierName() }} website.</span>\n </span>\n }\n </span>\n <strong>{{ fmt(item.amount) }}</strong>\n </div>\n }\n } @else {\n\n @if (isFullWalletRefundAllowed() || isWalletOnlyRefund() || isLoyaltyOnlyRefund()) {\n <button type=\"button\" class=\"sbd-cancel__method-option\"\n [ngClass]=\"{ 'sbd-cancel__method-option--active': preferredRefundMethod() === 'wallet' }\"\n (click)=\"preferredRefundMethod.set('wallet')\">\n <span class=\"sbd-cancel__method-icon\"><i class=\"pi pi-wallet\"></i></span>\n <span class=\"sbd-cancel__method-body-text\">\n <span class=\"sbd-cancel__method-name\">{{ walletCardLabel() }}</span>\n <span class=\"sbd-cancel__method-desc\">Usable for future bookings</span>\n <span class=\"sbd-cancel__method-badge sbd-cancel__method-badge--instant\">\n <svg width=\"12\" height=\"9\" viewBox=\"0 0 12 9\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"M7.31818 0C8.42727 0 9.49093 0.440583 10.2752 1.22483C11.0594 2.00907 11.5 3.07273 11.5 4.18182C11.5 5.2909 11.0594 6.35457 10.2752 7.13881C9.49093 7.92305 8.42727 8.36364 7.31818 8.36364C6.20909 8.36364 5.14543 7.92305 4.36119 7.13881C3.57695 6.35457 3.13636 5.2909 3.13636 4.18182C3.13636 3.07273 3.57695 2.00907 4.36119 1.22483C5.14543 0.440583 6.20909 0 7.31818 0ZM7.31818 1.04545C6.48637 1.04545 5.68862 1.37589 5.10044 1.96407C4.51226 2.55226 4.18182 3.35 4.18182 4.18182C4.18182 5.01363 4.51226 5.81138 5.10044 6.39956C5.68862 6.98774 6.48637 7.31818 7.31818 7.31818C8.15 7.31818 8.94774 6.98774 9.53593 6.39956C10.1241 5.81138 10.4545 5.01363 10.4545 4.18182C10.4545 3.35 10.1241 2.55226 9.53593 1.96407C8.94774 1.37589 8.15 1.04545 7.31818 1.04545ZM6.79545 2.09091H7.57955V4.06682L8.7975 5.28477L8.24341 5.83886L6.79545 4.39091V2.09091ZM0.522727 7.31818C0.384091 7.31818 0.251134 7.26311 0.153103 7.16508C0.0550729 7.06705 0 6.93409 0 6.79545C0 6.65682 0.0550729 6.52386 0.153103 6.42583C0.251134 6.3278 0.384091 6.27273 0.522727 6.27273H2.52477C2.68682 6.64386 2.89591 6.99409 3.13636 7.31818H0.522727ZM1.04545 4.70455C0.906819 4.70455 0.773861 4.64947 0.67583 4.55144C0.5778 4.45341 0.522727 4.32045 0.522727 4.18182C0.522727 4.04318 0.5778 3.91022 0.67583 3.81219C0.773861 3.71416 0.906819 3.65909 1.04545 3.65909H2.11705L2.09091 4.18182L2.11705 4.70455H1.04545ZM1.56818 2.09091C1.42955 2.09091 1.29659 2.03584 1.19856 1.93781C1.10053 1.83978 1.04545 1.70682 1.04545 1.56818C1.04545 1.42955 1.10053 1.29659 1.19856 1.19856C1.29659 1.10053 1.42955 1.04545 1.56818 1.04545H3.13636C2.89591 1.36955 2.68682 1.71977 2.52477 2.09091H1.56818Z\"\n fill=\"#15803C\"\n />\n </svg>\n Instant Refund\n </span>\n @if (hasLoyaltyPointsRefund() && !hasCashWalletRefund()) {\n <span class=\"sbd-cancel__notice sbd-cancel__notice--info\">\n <i class=\"pi pi-info-circle\"></i>\n <span>External loyalty points are refunded to your almatar Wallet as permanent points and ready to use in your next booking.</span>\n </span>\n }\n </span>\n <span class=\"sbd-cancel__method-radio\"></span>\n </button>\n }\n\n <!-- Original payment method \u2014 replaced with a \"Split Refund\" card whenever\n part of the refund is forced to the wallet (e.g. loyalty points used),\n since the total can't go 100% to the original payment method.\n Name/desc/badge/icon become gateway-specific for installment (Tamara,\n Tabby) and loyalty (Al Rajhi Mokafaa) payment methods. -->\n @if (!hasSplitRefund() && !isWalletOnlyRefund() && !isLoyaltyOnlyRefund()) {\n <button type=\"button\" class=\"sbd-cancel__method-option\"\n [ngClass]=\"{ 'sbd-cancel__method-option--active': preferredRefundMethod() === 'originalPaymentMethod' }\"\n (click)=\"preferredRefundMethod.set('originalPaymentMethod')\">\n <span class=\"sbd-cancel__method-icon\">\n <i class=\"pi\" [ngClass]=\"isSpecialGatewayMethod() ? 'pi-wallet' : 'pi-credit-card'\"></i>\n </span>\n <span class=\"sbd-cancel__method-body-text\">\n <span class=\"sbd-cancel__method-name\">{{ originalPaymentLabel() }}</span>\n <span class=\"sbd-cancel__method-desc\">\n @if (isInstallmentMethod()) {\n Returned to your {{ customerPaymentLabel() }} installment plan\n } @else if (isLoyaltyMethod()) {\n Refunded to your {{ customerPaymentLabel() }} account\n } @else {\n Returned to your original payment method\n }\n </span>\n <span class=\"sbd-cancel__method-badge\" [ngClass]=\"isLoyaltyMethod() ? 'sbd-cancel__method-badge--loyalty' : 'sbd-cancel__method-badge--days'\">\n @if (!isLoyaltyMethod()) {\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"M3.30078 6.50195C3.20312 6.50195 3.12174 6.4694 3.05664 6.4043C2.99154 6.33919 2.95898 6.25781 2.95898 6.16016C2.95898 6.06576 2.99154 5.986 3.05664 5.9209C3.12174 5.85579 3.20312 5.82324 3.30078 5.82324H5.5127V2.86914C5.5127 2.77474 5.54525 2.69499 5.61035 2.62988C5.67546 2.56478 5.75521 2.53223 5.84961 2.53223C5.94727 2.53223 6.02865 2.56478 6.09375 2.62988C6.15885 2.69499 6.19141 2.77474 6.19141 2.86914V6.16016C6.19141 6.25781 6.15885 6.33919 6.09375 6.4043C6.02865 6.4694 5.94727 6.50195 5.84961 6.50195H3.30078ZM5.85449 10.9551C5.16764 10.9551 4.52311 10.8249 3.9209 10.5645C3.31868 10.3073 2.78971 9.95085 2.33398 9.49512C1.87826 9.03939 1.52018 8.51042 1.25977 7.9082C1.0026 7.30599 0.874023 6.66146 0.874023 5.97461C0.874023 5.28776 1.0026 4.64323 1.25977 4.04102C1.52018 3.4388 1.87826 2.90983 2.33398 2.4541C2.78971 1.99512 3.31868 1.63704 3.9209 1.37988C4.52311 1.12272 5.16764 0.994141 5.85449 0.994141C6.54134 0.994141 7.18587 1.12272 7.78809 1.37988C8.3903 1.63704 8.91927 1.99512 9.375 2.4541C9.83073 2.90983 10.1872 3.4388 10.4443 4.04102C10.7048 4.64323 10.835 5.28776 10.835 5.97461C10.835 6.66146 10.7048 7.30599 10.4443 7.9082C10.1872 8.51042 9.83073 9.03939 9.375 9.49512C8.91927 9.95085 8.3903 10.3073 7.78809 10.5645C7.18587 10.8249 6.54134 10.9551 5.85449 10.9551ZM5.85449 10.125C6.42741 10.125 6.96452 10.0176 7.46582 9.80273C7.96712 9.58789 8.4082 9.29004 8.78906 8.90918C9.16992 8.52832 9.46777 8.08724 9.68262 7.58594C9.89746 7.08464 10.0049 6.54753 10.0049 5.97461C10.0049 5.40169 9.89746 4.86458 9.68262 4.36328C9.46777 3.85872 9.16992 3.41764 8.78906 3.04004C8.4082 2.65918 7.96712 2.36133 7.46582 2.14648C6.96452 1.93164 6.42741 1.82422 5.85449 1.82422C5.28158 1.82422 4.74447 1.93164 4.24316 2.14648C3.74186 2.36133 3.30078 2.65918 2.91992 3.04004C2.53906 3.41764 2.24121 3.85872 2.02637 4.36328C1.81152 4.86458 1.7041 5.40169 1.7041 5.97461C1.7041 6.54753 1.81152 7.08464 2.02637 7.58594C2.24121 8.08724 2.53906 8.52832 2.91992 8.90918C3.30078 9.29004 3.74186 9.58789 4.24316 9.80273C4.74447 10.0176 5.28158 10.125 5.85449 10.125Z\"\n fill=\"#0B54C2\"\n />\n </svg>\n } @else {\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <circle cx=\"6\" cy=\"6\" r=\"5\" stroke=\"#0B54C2\" stroke-width=\"1.2\" />\n <path d=\"M6 5.5v3M6 3.9v.01\" stroke=\"#0B54C2\" stroke-width=\"1.2\" stroke-linecap=\"round\" />\n </svg>\n }\n {{ cardBadgeText() }}\n </span>\n </span>\n <span class=\"sbd-cancel__option-end\">\n @if (isMadaMethod()) {\n <span class=\"sbd-cancel__mada-logo\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"madaLogo\"></ng-container>\n </span>\n }\n <span class=\"sbd-cancel__method-radio\"></span>\n </span>\n </button>\n }\n\n <!-- Split Refund -->\n @if (hasSplitRefund()) {\n <button type=\"button\" class=\"sbd-cancel__method-option sbd-cancel__method-option--split\"\n [ngClass]=\"{ 'sbd-cancel__method-option--active': preferredRefundMethod() === 'split' }\"\n (click)=\"preferredRefundMethod.set('split')\">\n <span class=\"sbd-cancel__method-icon\"><i class=\"pi pi-wallet\"></i></span>\n <span class=\"sbd-cancel__method-body-text\">\n <span class=\"sbd-cancel__method-name\">Split Refund</span>\n <span class=\"sbd-cancel__method-desc\">Returned to your original payment method</span>\n </span>\n <strong class=\"sbd-cancel__method-split-total\">{{ fmt(adjustedData()?.totalRefundAmount) }}</strong>\n <span class=\"sbd-cancel__method-radio\"></span>\n\n <span class=\"sbd-cancel__method-split-breakdown\">\n @for (row of splitRefundRows(); track row.label) {\n <span class=\"sbd-cancel__method-split-row\">\n <span class=\"sbd-cancel__method-split-label\">\n {{ row.label }}\n @if (row.isMada) {\n <span class=\"sbd-cancel__mada-logo\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"madaLogo\"></ng-container>\n </span>\n }\n </span>\n <strong>{{ fmt(row.value) }}</strong>\n </span>\n <span class=\"sbd-cancel__method-badge\"\n [ngClass]=\"row.instant ? 'sbd-cancel__method-badge--instant' : (isLoyaltyMethod() ? 'sbd-cancel__method-badge--loyalty' : 'sbd-cancel__method-badge--days')\">\n {{ row.badgeText }}\n </span>\n @if (row.isLoyalty) {\n <span class=\"sbd-cancel__notice sbd-cancel__notice--info\">\n <i class=\"pi pi-info-circle\"></i>\n <span>External loyalty points are refunded to your almatar Wallet as permanent points and ready to use in your next booking.</span>\n </span>\n }\n }\n </span>\n </button>\n }\n }\n </div>\n\n <div class=\"sbd-cancel__footer\">\n <button pButton\n styleClass=\"sbd-cancel__btn-danger-pill\"\n [label]=\"cancelling() ? 'Cancelling...' : 'Confirm Cancellation'\"\n [disabled]=\"cancelling()\" (click)=\"confirm()\"></button>\n </div>\n }\n\n <!-- Main card -->\n @else if (refundData() && step() === 'details') {\n <!-- Card header -->\n <div class=\"sbd-cancel__header sbd-cancel__header--stack\">\n <div class=\"sbd-cancel__header-titles\">\n <span class=\"sbd-cancel__header-title\">Cancel Booking Request</span>\n <span class=\"sbd-cancel__header-sub\">#{{ refundData()?.bookingId ?? bookingId() }}</span>\n </div>\n <button type=\"button\" class=\"sbd-cancel__close-btn\" (click)=\"closed.emit()\" aria-label=\"Close\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n\n <div class=\"sbd-cancel__body\">\n\n <!-- Non-refundable -->\n @if (!refundData()?.isRefundable) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--warning\">\n <i class=\"pi pi-exclamation-triangle\"></i>\n <div>\n <strong>This booking is not eligible for a refund.</strong>\n @if (refundData()?.cause) {\n <p>{{ refundData()?.cause }}</p>\n } @else {\n <p>Please escalate to the relevant team for manual handling.</p>\n }\n </div>\n </div>\n }\n\n <!-- Refundable flow -->\n @else {\n\n <!-- Top summary: paid / get back -->\n <div class=\"sbd-cancel__summary\">\n <div class=\"sbd-cancel__summary-row\"\n [ngClass]=\"{ 'sbd-cancel__summary-row--toggle': hasPaidBreakdown() && !!adjustedData()?.totalWalletAmount }\"\n (click)=\"hasPaidBreakdown() && !!adjustedData()?.totalWalletAmount && togglePaidSummary()\">\n <span class=\"sbd-cancel__summary-label\"><i class=\"pi pi-wallet\"></i> Total paid</span>\n <span class=\"sbd-cancel__summary-value\">\n <strong>{{ fmt(adjustedData()?.originalAmount) }}</strong>\n @if (hasPaidBreakdown() && !!adjustedData()?.totalWalletAmount) {\n <button type=\"button\" class=\"sbd-cancel__leg-chevron\"\n [ngClass]=\"{ 'sbd-cancel__leg-chevron--collapsed': !paidSummaryExpanded() }\"\n aria-label=\"Toggle paid breakdown\">\n <i class=\"pi pi-chevron-up\"></i>\n </button>\n }\n </span>\n </div>\n\n @if (hasPaidBreakdown() && paidSummaryExpanded() && !!adjustedData()?.totalWalletAmount) {\n <div class=\"sbd-cancel__summary-breakdown\">\n @for (row of paidBreakdownRows(); track row.label) {\n @if (row.label === 'Almatar Wallet') {\n <div class=\"sbd-cancel__summary-breakdown-row\">\n <span>{{ row.label }}</span>\n <span>{{ fmt(row.value) }}</span>\n </div>\n }\n }\n </div>\n }\n\n <div class=\"sbd-cancel__summary-row sbd-cancel__summary-row--refund\">\n <span class=\"sbd-cancel__summary-label\"><i class=\"pi pi-dollar\"></i> You will get back</span>\n <strong>{{ fmt(adjustedData()?.totalRefundAmount) }}</strong>\n </div>\n </div>\n\n <!-- Golden Guarantee gift \u2014 overrides credit-shell messaging below -->\n @if (hasGoldenGuaranteeGift()) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--success\">\n <i class=\"pi pi-shield\"></i>\n <span>You're covered by Cancel for any reason service, you'll get 100% refund, minus any applicable fees. See the breakdown below.</span>\n </div>\n }\n\n <!-- One-way (or a round-trip restricted to a single leg): single card -->\n @if (!showRoundTripLegCards()) {\n <!-- Source selector (multiple suppliers) -->\n @if (availableSources().length > 1) {\n <div class=\"sbd-cancel__source-selector\">\n <span class=\"sbd-cancel__source-label\">Cancel via</span>\n @for (src of availableSources(); track src) {\n <button class=\"sbd-cancel__source-chip\"\n [ngClass]=\"{\n 'sbd-cancel__source-chip--active': selectedSource() === src,\n 'sbd-cancel__source-chip--no-refund': activeSourceBreakdown()?.[src]?.isRefundable === false\n }\"\n (click)=\"selectedSource.set(src)\">\n {{ src }}\n @if (activeSourceBreakdown()?.[src]?.isRefundable === false) {\n <i class=\"pi pi-ban\" style=\"font-size:10px; margin-left:4px;\"></i>\n }\n </button>\n }\n </div>\n @if (sourceNonRefundableHint()) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--warning\">\n <i class=\"pi pi-ban\"></i>\n <span>{{ sourceNonRefundableHint() }}</span>\n </div>\n }\n }\n\n <!-- Not refundable -->\n @if (!currentData()?.isRefundable) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--warning\">\n <i class=\"pi pi-ban\"></i>\n <div>\n <strong>This leg is not eligible for a refund.</strong>\n <p>Please escalate to the relevant team for manual handling.</p>\n </div>\n </div>\n } @else {\n <div class=\"sbd-cancel__section-title\">Refund Details</div>\n\n <div class=\"sbd-cancel__refund-card\">\n <span class=\"sbd-cancel__badge sbd-cancel__badge--outline sbd-cancel__badge--green\">Refundable</span>\n\n @if (refundableBreakdownRows(adjustedData()).length) {\n @for (r of refundableBreakdownRows(adjustedData()); track r.label) {\n <div class=\"sbd-cancel__refund-card-row\">\n <span>{{ r.label }}</span>\n <strong>{{ fmt(r.value) }}</strong>\n </div>\n }\n } @else {\n <div class=\"sbd-cancel__refund-card-row\">\n <span>Fare</span>\n <strong>{{ fmt(adjustedData()?.originalAmount) }}</strong>\n </div>\n }\n\n <!-- hide addon note new CR from CX -->\n <!-- @if (hasRefundableAddonNote(adjustedData())) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--neutral\">\n <i class=\"pi pi-info-circle\"></i>\n <span>Your add-on service/s refund will be processed separately by our Customer Service team shortly.</span>\n </div>\n } -->\n\n <!-- Deductions -->\n @if (deductions().length) {\n <div class=\"sbd-cancel__refund-card-divider\"></div>\n <span class=\"sbd-cancel__badge sbd-cancel__badge--outline sbd-cancel__badge--muted\">Not Refunded - Deductions</span>\n @for (d of deductions(); track d.label) {\n <div class=\"sbd-cancel__refund-card-line\">\n <span>\n {{ d.label }}\n @if (d.badge) { <span class=\"sbd-cancel__tag\">{{ d.badge }}</span> }\n </span>\n <span class=\"sbd-cancel__refund-card-line--neg\">{{ fmtNeg(d.value) }}</span>\n </div>\n }\n }\n\n <div class=\"sbd-cancel__refund-card-divider sbd-cancel__refund-card-divider--dashed\"></div>\n <div class=\"sbd-cancel__refund-card-total\">\n <span>Total Refund</span>\n <strong>{{ fmt(adjustedData()?.totalRefundAmount) }}</strong>\n </div>\n </div>\n\n <!-- Credit shell -->\n @if (creditShellNote()) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--credit\">\n <i class=\"pi pi-wallet\"></i>\n <span>\u2139\uFE0F {{ creditShellNote() }}</span>\n </div>\n }\n }\n }\n\n <!-- Round trip: one collapsible card per leg (only when both legs are cancellable) -->\n @if (showRoundTripLegCards()) {\n <!-- Gift hint -->\n @if (giftHint()) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--tip\">\n <i class=\"pi pi-info-circle\"></i>\n <span>{{ giftHint() }}</span>\n </div>\n }\n\n <!-- Mixed hint -->\n @if (mixedHint()) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--warning\">\n <i class=\"pi pi-exclamation-triangle\"></i>\n <span>{{ mixedHint() }}</span>\n </div>\n }\n\n <!-- Source selector (multiple suppliers) -->\n @if (availableSources().length > 1) {\n <div class=\"sbd-cancel__source-selector\">\n <span class=\"sbd-cancel__source-label\">Cancel via</span>\n @for (src of availableSources(); track src) {\n <button class=\"sbd-cancel__source-chip\"\n [ngClass]=\"{\n 'sbd-cancel__source-chip--active': selectedSource() === src,\n 'sbd-cancel__source-chip--no-refund': activeSourceBreakdown()?.[src]?.isRefundable === false\n }\"\n (click)=\"selectedSource.set(src)\">\n {{ src }}\n @if (activeSourceBreakdown()?.[src]?.isRefundable === false) {\n <i class=\"pi pi-ban\" style=\"font-size:10px; margin-left:4px;\"></i>\n }\n </button>\n }\n </div>\n @if (sourceNonRefundableHint()) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--warning\">\n <i class=\"pi pi-ban\"></i>\n <span>{{ sourceNonRefundableHint() }}</span>\n </div>\n }\n }\n\n <!-- Onward leg card -->\n @if (onwardChecked()) {\n <div class=\"sbd-cancel__leg-card\">\n <div class=\"sbd-cancel__leg-card-header\" (click)=\"toggleLegExpanded('onward')\">\n <span class=\"sbd-cancel__leg-checkbox\">Onward</span>\n <button type=\"button\" class=\"sbd-cancel__leg-chevron\"\n [ngClass]=\"{ 'sbd-cancel__leg-chevron--collapsed': !onwardExpanded() }\"\n aria-label=\"Toggle\">\n <i class=\"pi pi-chevron-up\"></i>\n </button>\n </div>\n\n @if (onwardExpanded()) {\n <div class=\"sbd-cancel__leg-card-body\">\n <span class=\"sbd-cancel__badge sbd-cancel__badge--outline\"\n [ngClass]=\"legAdjustedData('onward')?.isRefundable ? 'sbd-cancel__badge--green' : 'sbd-cancel__badge--red'\">\n {{ legAdjustedData('onward')?.isRefundable ? 'Refundable' : 'Non-Refundable' }}\n </span>\n\n @if (legAdjustedData('onward')?.isRefundable) {\n @if (refundableBreakdownRows(legAdjustedData('onward')).length) {\n @for (r of refundableBreakdownRows(legAdjustedData('onward')); track r.label) {\n <div class=\"sbd-cancel__refund-card-row\">\n <span>{{ r.label }}</span>\n <strong>{{ fmt(r.value) }}</strong>\n </div>\n }\n } @else {\n <div class=\"sbd-cancel__refund-card-row\">\n <span>Fare</span>\n <strong>{{ fmt(legRefundPool('onward')) }}</strong>\n </div>\n }\n\n <!-- hide addon note new CR from CX -->\n <!-- @if (hasRefundableAddonNote(legAdjustedData('onward'))) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--neutral\">\n <i class=\"pi pi-info-circle\"></i>\n <span>Your add-on service/s refund will be processed separately by our Customer Service team shortly.</span>\n </div>\n } -->\n\n @if (legDeductions('onward').length && !isCreditShell()) {\n <div class=\"sbd-cancel__refund-card-divider\"></div>\n <span class=\"sbd-cancel__badge sbd-cancel__badge--outline sbd-cancel__badge--muted\">Not Refunded - Deductions</span>\n @for (d of legDeductions('onward'); track d.label) {\n <div class=\"sbd-cancel__refund-card-line\">\n <span>\n {{ d.label }}\n @if (d.badge) { <span class=\"sbd-cancel__tag\">{{ d.badge }}</span> }\n </span>\n <span class=\"sbd-cancel__refund-card-line--neg\">{{ fmtNeg(d.value) }}</span>\n </div>\n }\n }\n\n <div class=\"sbd-cancel__refund-card-divider sbd-cancel__refund-card-divider--dashed\"></div>\n <div class=\"sbd-cancel__refund-card-total\">\n <span>Departure Total Refund</span>\n <strong>{{ fmt(legAdjustedData('onward')?.totalRefundAmount) }}</strong>\n </div>\n\n @if (refundData()?.legInfo?.onward?.isCreditShell && !hasGoldenGuaranteeGift()) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--credit\">\n <i class=\"pi pi-wallet\"></i>\n <span>\u2139\uFE0F The refund for the onward flight has been issued as an Airline Credit Shell.</span>\n </div>\n }\n } @else {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--warning\">\n <i class=\"pi pi-ban\"></i>\n <span>This leg is not eligible for a refund.</span>\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Return leg card -->\n @if (returnChecked()) {\n <div class=\"sbd-cancel__leg-card\">\n <div class=\"sbd-cancel__leg-card-header\" (click)=\"toggleLegExpanded('return')\">\n <span class=\"sbd-cancel__leg-checkbox\">Return</span>\n <button type=\"button\" class=\"sbd-cancel__leg-chevron\"\n [ngClass]=\"{ 'sbd-cancel__leg-chevron--collapsed': !returnExpanded() }\"\n aria-label=\"Toggle\">\n <i class=\"pi pi-chevron-up\"></i>\n </button>\n </div>\n\n @if (returnExpanded()) {\n <div class=\"sbd-cancel__leg-card-body\">\n <span class=\"sbd-cancel__badge sbd-cancel__badge--outline\"\n [ngClass]=\"legAdjustedData('return')?.isRefundable ? 'sbd-cancel__badge--green' : 'sbd-cancel__badge--red'\">\n {{ legAdjustedData('return')?.isRefundable ? 'Refundable' : 'Non-Refundable' }}\n </span>\n\n @if (legAdjustedData('return')?.isRefundable) {\n @if (refundableBreakdownRows(legAdjustedData('return')).length) {\n @for (r of refundableBreakdownRows(legAdjustedData('return')); track r.label) {\n <div class=\"sbd-cancel__refund-card-row\">\n <span>{{ r.label }}</span>\n <strong>{{ fmt(r.value) }}</strong>\n </div>\n }\n } @else {\n <div class=\"sbd-cancel__refund-card-row\">\n <span>Fare</span>\n <strong>{{ fmt(legRefundPool('return')) }}</strong>\n </div>\n }\n\n <!-- hide addon note new CR from CX -->\n <!-- @if (hasRefundableAddonNote(legAdjustedData('return'))) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--neutral\">\n <i class=\"pi pi-info-circle\"></i>\n <span>Your add-on service/s refund will be processed separately by our Customer Service team shortly.</span>\n </div>\n } -->\n\n @if (legDeductions('return').length && !isCreditShell()) {\n <div class=\"sbd-cancel__refund-card-divider\"></div>\n <span class=\"sbd-cancel__badge sbd-cancel__badge--outline sbd-cancel__badge--muted\">Not Refunded - Deductions</span>\n @for (d of legDeductions('return'); track d.label) {\n <div class=\"sbd-cancel__refund-card-line\">\n <span>\n {{ d.label }}\n @if (d.badge) { <span class=\"sbd-cancel__tag\">{{ d.badge }}</span> }\n </span>\n <span class=\"sbd-cancel__refund-card-line--neg\">{{ fmtNeg(d.value) }}</span>\n </div>\n }\n }\n\n <div class=\"sbd-cancel__refund-card-divider sbd-cancel__refund-card-divider--dashed\"></div>\n <div class=\"sbd-cancel__refund-card-total\">\n <span>Return Total Refund</span>\n <strong>{{ fmt(legAdjustedData('return')?.totalRefundAmount) }}</strong>\n </div>\n\n @if (refundData()?.legInfo?.return?.isCreditShell && !hasGoldenGuaranteeGift()) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--credit\">\n <i class=\"pi pi-wallet\"></i>\n <span>\u2139\uFE0F The refund for the return flight has been issued as an Airline Credit Shell.</span>\n </div>\n }\n } @else {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--warning\">\n <i class=\"pi pi-ban\"></i>\n <span>This leg is not eligible for a refund.</span>\n </div>\n }\n </div>\n }\n </div>\n }\n }\n\n <!-- Instant refund notice -->\n @if (currentData()?.isRefundable) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--shield\">\n <i class=\"pi pi-shield\"></i>\n <span>\n @if (isNilRefund()) {\n We're sorry, as per airline policy, no refund applies because the cancellation fees equal or exceed your refundable fare amount.\n } @else {\n @if (isWalletOnlyRefund() || isLoyaltyOnlyRefund()) {\n Your refund is instant to the customer's almatar Wallet.\n } @else {\n Your refund is instant from our side, your bank may take up to 15 business days to reflect it.\n }\n }\n @if (!isNilRefund() && !isCreditShell()) {\n <strong>You can select refund option in the next step</strong>\n }\n </span>\n </div>\n }\n }\n\n </div>\n\n <!-- Footer -->\n <div class=\"sbd-cancel__footer\">\n @if (refundData()?.isRefundable) {\n <button pButton label=\"Keep booking\" styleClass=\"p-button-outlined p-button-secondary sbd-cancel__btn-keep\" (click)=\"closed.emit()\"></button>\n @if (currentData()?.isRefundable) {\n <button pButton\n styleClass=\"sbd-cancel__btn-primary\"\n [label]=\"isCreditShell() ? 'Continue Cancellation' : 'Select refund option'\"\n [disabled]=\"!canConfirm()\" (click)=\"requestCancel()\"></button>\n }\n } @else {\n <button pButton label=\"Close\" styleClass=\"p-button-outlined p-button-secondary\" (click)=\"closed.emit()\"></button>\n }\n </div>\n }\n\n <!-- \u2500\u2500 Result overlay: floating modal over the dimmed refund-method screen (scoped to this dialog, not the whole viewport) \u2500\u2500 -->\n @if (cancelResult()) {\n <div class=\"sbd-cancel__result-overlay\">\n <div class=\"sbd-cancel__result-backdrop\"></div>\n <div class=\"sbd-cancel__result-modal\">\n <div class=\"sbd-cancel__result-hero\">\n <span class=\"sbd-cancel__result-icon sbd-cancel__result-icon--green\">\n <i class=\"pi pi-check\"></i>\n </span>\n <h3 class=\"sbd-cancel__result-title\">{{ confirmIsNilRefund() ? 'Cancel Confirmed' : 'Refund Confirmed' }}</h3>\n </div>\n\n @for (r of confirmRefunds(); track r.label) {\n <div class=\"sbd-cancel__result-row\">\n <span class=\"sbd-cancel__method-icon\">\n <i class=\"pi\" [ngClass]=\"isWalletDestinationRow(r) ? 'pi-wallet' : 'pi-credit-card'\"></i>\n </span>\n <span class=\"sbd-cancel__result-body-text\">\n <span class=\"sbd-cancel__method-name\">{{ r.label }}</span>\n @if (r.subRows?.length) {\n <span class=\"sbd-cancel__result-sub-rows\">\n @for (sub of r.subRows; track sub.label) {\n <span class=\"sbd-cancel__result-sub-row\">\n <span>{{ sub.label }}</span>\n <strong>{{ fmtResult(sub.value) }}</strong>\n </span>\n }\n </span>\n }\n <span class=\"sbd-cancel__method-badge\"\n [ngClass]=\"isWalletDestinationRow(r) ? 'sbd-cancel__method-badge--instant' : 'sbd-cancel__method-badge--days'\">\n {{ isWalletDestinationRow(r) ? 'Ready Now' : (r.key === 'totalCardRefundAmount' ? confirmCardBadgeText() : 'Up to 15 business days, bank policy') }}\n </span>\n @if (r.hasLoyaltyNote) {\n <span class=\"sbd-cancel__notice sbd-cancel__notice--info\">\n <i class=\"pi pi-info-circle\"></i>\n <span>External loyalty points are refunded to your almatar Wallet as permanent points and ready to use in your next booking.</span>\n </span>\n }\n </span>\n <strong class=\"sbd-cancel__result-amount\">{{ fmtResult(r.value) }}</strong>\n </div>\n }\n\n @if (confirmCreditShellNote()) {\n <div class=\"sbd-cancel__notice sbd-cancel__notice--credit\">\n <i class=\"pi pi-wallet\"></i>\n <span>\u2139\uFE0F {{ confirmCreditShellNote() }}</span>\n </div>\n }\n\n <button pButton styleClass=\"sbd-cancel__btn-primary sbd-cancel__result-done\" label=\"Close\" (click)=\"closed.emit()\"></button>\n </div>\n </div>\n }\n\n </div>\n\n</p-dialog>\n\n<ng-template #madaLogo>\n <svg width=\"43\" height=\"15\" viewBox=\"0 0 43 15\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <g clip-path=\"url(#clip0_sbd_mada)\">\n <path d=\"M18.1894 8.6543H0V14.9933H18.1894V8.6543Z\" fill=\"#84B740\" />\n <path d=\"M18.1894 0H0V6.34463H18.1894V0Z\" fill=\"#259BD6\" />\n <path\n d=\"M36.3779 13.7034L36.2969 13.7203C36.016 13.7768 35.9134 13.7994 35.7082 13.7994C35.233 13.7994 34.6713 13.5452 34.6713 12.3475C34.6713 11.7316 34.7685 10.9124 35.6542 10.9124H35.6596C35.8108 10.9237 35.9836 10.9407 36.3077 11.0424L36.3779 11.065V13.7034ZM36.5237 7.73164L36.3779 7.75989V9.97458L36.2483 9.93503L36.2105 9.92373C36.0647 9.87853 35.7298 9.77684 35.4058 9.77684C33.6344 9.77684 33.2617 11.178 33.2617 12.3531C33.2617 13.9633 34.1258 14.8898 35.6326 14.8898C36.2699 14.8898 36.7397 14.822 37.215 14.6582C37.6524 14.5113 37.8091 14.3023 37.8091 13.8559V7.5C37.3878 7.5791 36.9504 7.65819 36.5237 7.73164Z\"\n fill=\"#747583\"\n />\n <path\n d=\"M41.651 13.7385L41.5754 13.7611L41.3053 13.8345C41.0515 13.9023 40.8247 13.9419 40.6519 13.9419C40.236 13.9419 39.9876 13.7272 39.9876 13.36C39.9876 13.1227 40.0902 12.7215 40.7707 12.7215H41.651V13.7385ZM41.0299 9.74414C40.4844 9.74414 39.9228 9.84584 39.2261 10.0718L38.7724 10.2131L38.9236 11.2865L39.3665 11.134C39.831 10.9758 40.4088 10.8741 40.8409 10.8741C41.0353 10.8741 41.6294 10.8741 41.6294 11.5464V11.8402H40.8031C39.2963 11.8402 38.5996 12.343 38.5996 13.4221C38.5996 14.343 39.2423 14.8967 40.3224 14.8967C40.6573 14.8967 41.1217 14.8289 41.5214 14.7272L41.543 14.7215L41.5646 14.7272L41.6996 14.7498C42.1208 14.8289 42.5583 14.908 42.9849 14.9927V11.4673C42.9849 10.3261 42.3261 9.74414 41.0299 9.74414Z\"\n fill=\"#747583\"\n />\n <path\n d=\"M31.1527 13.7385L31.0771 13.7611L30.807 13.8345C30.5532 13.9023 30.3318 13.9419 30.1536 13.9419C29.7377 13.9419 29.4893 13.7272 29.4893 13.36C29.4893 13.1227 29.5919 12.7215 30.267 12.7215H31.1473L31.1527 13.7385ZM30.537 9.74414C29.9861 9.74414 29.4299 9.84584 28.7332 10.0718L28.2795 10.2131L28.4308 11.2865L28.8736 11.134C29.3381 10.9758 29.9159 10.8741 30.348 10.8741C30.5424 10.8741 31.1365 10.8741 31.1365 11.5464V11.8402H30.3102C28.8034 11.8402 28.1013 12.343 28.1013 13.4221C28.1013 14.343 28.744 14.8967 29.8295 14.8967C30.1644 14.8967 30.6288 14.8289 31.0285 14.7272L31.0501 14.7215L31.0717 14.7272L31.2013 14.7498C31.6279 14.8289 32.06 14.908 32.4866 14.9984V11.473C32.492 10.3204 31.8332 9.74414 30.537 9.74414Z\"\n fill=\"#747583\"\n />\n <path\n d=\"M25.4701 9.7576C24.7842 9.7576 24.2172 9.99489 24.0065 10.0966L23.9525 10.1248L23.9039 10.0853C23.6123 9.86495 23.1856 9.75195 22.5916 9.75195C22.0677 9.75195 21.5762 9.83105 21.0416 9.99489C20.5825 10.1418 20.4043 10.3734 20.4043 10.8085V14.8367H21.8409V11.1135L21.9111 11.0909C22.2027 10.9892 22.3755 10.9723 22.543 10.9723C22.9588 10.9723 23.1694 11.2039 23.1694 11.6559V14.8423H24.5844V11.5938C24.5844 11.4017 24.5466 11.2887 24.5412 11.2661L24.4926 11.17L24.5898 11.1248C24.8058 11.0231 25.0435 10.9723 25.2919 10.9723C25.5781 10.9723 25.9184 11.0909 25.9184 11.6559V14.8423H27.3279V11.509C27.3279 10.3282 26.7231 9.7576 25.4701 9.7576Z\"\n fill=\"#747583\"\n />\n <path\n d=\"M40.5866 4.13559C40.3759 4.13559 40.0249 4.113 39.7495 4.0565L39.6684 4.03955V1.86441C39.6684 1.68362 39.636 1.57062 39.6306 1.55367L39.5874 1.46328L39.6793 1.42373C39.7009 1.41243 39.7225 1.40678 39.7495 1.39548L39.7657 1.38418C39.7981 1.37288 39.8305 1.36158 39.8629 1.35028C39.8791 1.34463 39.8899 1.33898 39.9007 1.33898C40.2193 1.24859 40.511 1.25989 40.6406 1.24859H40.646C41.5263 1.24859 41.6289 2.0678 41.6289 2.68362C41.6235 3.88136 41.0564 4.13559 40.5866 4.13559ZM40.5812 0C40.5704 0 40.5542 0 40.5434 0C39.7171 0 38.8692 0.237288 38.5667 0.700565C38.4047 0.926554 38.3129 1.20904 38.3075 1.54237V3.78531C38.3075 3.9774 38.2697 4.05085 38.2643 4.0678L38.2157 4.16384H35.6072V2.60452H35.6018C35.5694 0.960452 34.6404 0.0564972 33.2957 0.0564972H33.1391H31.9833C31.9293 0.457627 31.8861 0.740113 31.8321 1.14124H33.1391C33.8249 1.14124 34.1868 1.75141 34.1868 2.68927V4.25989L34.095 4.20904C34.0788 4.20339 33.9654 4.16384 33.7871 4.16384H31.5297C31.4865 4.46328 31.4325 4.85311 31.373 5.24294H38.3129C38.5505 5.19209 38.8259 5.14689 39.0636 5.10735C39.4146 5.28814 40.0681 5.38418 40.5164 5.38418C42.0231 5.38418 43.0007 4.32768 43.0007 2.70057C42.9953 1.0904 42.0501 0.0338983 40.5812 0Z\"\n fill=\"#747583\"\n />\n <path\n d=\"M28.4189 5.90821H28.4837C29.9905 5.90821 30.6925 5.38844 30.6925 4.10595C30.6925 3.18505 30.0499 2.45059 28.9697 2.45059H27.5818C27.1659 2.45059 26.9175 2.202 26.9175 1.78392C26.9175 1.50143 27.0201 1.15115 27.7006 1.15115H30.7357C30.8006 0.738723 30.833 0.478836 30.8924 0.0664062H27.7384C26.2694 0.0664062 25.5295 0.710474 25.5295 1.78392C25.5295 2.84607 26.1722 3.39974 27.2523 3.39974H28.6403C29.0561 3.39974 29.3046 3.74437 29.3046 4.10595C29.3046 4.34324 29.202 4.83477 28.5269 4.83477H28.2947L23.8499 4.82347H23.0398C22.3539 4.82347 21.8733 4.41669 21.8733 3.47319V2.82347C21.8733 1.84042 22.2459 1.23025 23.0398 1.23025H24.3576C24.417 0.812169 24.4548 0.546632 24.5088 0.151152H22.867H22.7104C21.3656 0.151152 20.4367 1.09466 20.4043 2.73872V2.80087V3.47319C20.4367 5.11725 21.3656 5.90256 22.7104 5.90256H22.867H24.0227L26.4314 5.90821H27.868H28.4189Z\"\n fill=\"#747583\"\n />\n </g>\n <defs>\n <clipPath id=\"clip0_sbd_mada\">\n <rect width=\"43\" height=\"15\" fill=\"white\" />\n </clipPath>\n </defs>\n </svg>\n</ng-template>\n", styles: [":host ::ng-deep .sbd-cancel-dialog-wrapper{border-radius:16px;overflow:hidden;box-shadow:0 24px 60px #0f172a38}:host ::ng-deep .sbd-cancel-dialog-wrapper .p-dialog-content{padding:0;border-radius:16px;overflow-y:auto;overflow-x:hidden}.sbd-cancel{font-family:inherit;border-radius:16px;overflow:visible;position:relative}.sbd-cancel__loading{padding:24px}.sbd-cancel__loading-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.sbd-cancel__loading-rows{display:flex;flex-direction:column;gap:10px;margin-top:20px}.sbd-cancel__loading-row{display:flex;justify-content:space-between}.sbd-cancel__are-you-sure{padding:36px 24px 28px;display:flex;flex-direction:column;align-items:center;gap:14px;text-align:center;border-top:1px solid #e2e8f0}.sbd-cancel__ays-icon{width:56px;height:56px;border-radius:50%;background:#fff7ed;border:2px solid #fed7aa;display:flex;align-items:center;justify-content:center}.sbd-cancel__ays-icon .pi{font-size:24px;color:#f97316}.sbd-cancel__ays-icon--warn{background:#fef2f2;border-color:#fecaca}.sbd-cancel__ays-icon--warn .pi{color:#ef4444}.sbd-cancel__ays-hint{font-size:13px;color:#64748b;min-height:20px;animation:sbd-hint-fade .35s ease-out}.sbd-cancel__ays-dots{display:flex;gap:6px}.sbd-cancel__ays-dots span{width:7px;height:7px;border-radius:50%;background:#f97316;opacity:.3;animation:sbd-dot-pulse 1.2s ease-in-out infinite}.sbd-cancel__ays-dots span:nth-child(2){animation-delay:.2s}.sbd-cancel__ays-dots span:nth-child(3){animation-delay:.4s}@keyframes sbd-hint-fade{0%{opacity:0;transform:translateY(5px)}to{opacity:1;transform:translateY(0)}}@keyframes sbd-dot-pulse{0%,to{opacity:.3;transform:scale(1)}50%{opacity:1;transform:scale(1.4)}}.sbd-cancel__header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #e2e8f0;background:#f8fafc}.sbd-cancel__header-left{display:flex;align-items:center;gap:10px}.sbd-cancel__header-icon{font-size:18px;color:#475569}.sbd-cancel__header-title{font-size:15px;font-weight:700;color:#1e2e4a}.sbd-cancel__header--stack{align-items:flex-start}.sbd-cancel__header-titles{display:flex;flex-direction:column;gap:2px}.sbd-cancel__header-sub{font-size:12.5px;font-weight:600;color:#94a3b8;font-variant-numeric:tabular-nums}.sbd-cancel__badge{font-size:11.5px;font-weight:700;border-radius:20px;padding:3px 12px;letter-spacing:.03em;width:max-content;text-transform:uppercase}.sbd-cancel__badge--green{background:#dcfce7;color:#166534}.sbd-cancel__badge--red{background:#fee2e2;color:#991b1b}.sbd-cancel__badge--orange{background:#fff7ed;color:#9a3412}.sbd-cancel__badge--outline{background:transparent;box-shadow:inset 0 0 0 1.5px currentColor;padding:4px 14px}.sbd-cancel__badge--outline.sbd-cancel__badge--green{color:#16a34a}.sbd-cancel__badge--outline.sbd-cancel__badge--red{color:#dc2626}.sbd-cancel__badge--muted{background:#f8fafc;color:#64748b;box-shadow:inset 0 0 0 1px #e2e8f0}.sbd-cancel__close-btn{width:30px;height:30px;border-radius:8px;border:none;background:transparent;color:#64748b;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background .15s,color .15s}.sbd-cancel__close-btn .pi{font-size:14px}.sbd-cancel__close-btn:hover{background:#e2e8f0;color:#1e2e4a}.sbd-cancel__close-btn--floating{position:absolute;top:12px;inset-inline-end:12px}.sbd-cancel__summary{background:#f8fafc;border:1px solid #e2e8f0;border-radius:12px;padding:14px 16px;display:flex;flex-direction:column;gap:10px}.sbd-cancel__summary-row{display:flex;align-items:center;justify-content:space-between;font-size:13.5px}.sbd-cancel__summary-row strong{font-size:15px;font-weight:800;color:#1e2e4a;font-variant-numeric:tabular-nums}.sbd-cancel__summary-row--refund .sbd-cancel__summary-label,.sbd-cancel__summary-row--refund strong{color:#16a34a}.sbd-cancel__summary-row--toggle{cursor:pointer}.sbd-cancel__summary-label{display:inline-flex;align-items:center;gap:8px;font-weight:600;color:#475569}.sbd-cancel__summary-label .pi{font-size:13px}.sbd-cancel__summary-value{display:flex;align-items:center;gap:4px}.sbd-cancel__summary-breakdown{display:flex;flex-direction:column;gap:6px;padding:2px 0 2px 22px}.sbd-cancel__summary-breakdown-row{display:flex;align-items:center;justify-content:space-between;font-size:12.5px;color:#64748b}.sbd-cancel__summary-breakdown-row span:last-child{font-variant-numeric:tabular-nums;font-weight:600;color:#475569}.sbd-cancel__section-title{font-size:14px;font-weight:700;color:#1e2e4a}.sbd-cancel__refund-card{border:1px solid #e2e8f0;border-radius:12px;padding:16px;display:flex;flex-direction:column;gap:4px}.sbd-cancel__refund-card-row{display:flex;align-items:center;justify-content:space-between;margin-top:10px;font-size:14px}.sbd-cancel__refund-card-row span{color:#334155;font-weight:600}.sbd-cancel__refund-card-row strong{color:#1e2e4a;font-weight:700;font-variant-numeric:tabular-nums}.sbd-cancel__refund-card-divider{border-top:1px solid #e2e8f0;margin:10px 0}.sbd-cancel__refund-card-divider--dashed{border-top-style:dashed}.sbd-cancel__refund-card-line{display:flex;align-items:center;justify-content:space-between;padding:5px 0;font-size:13px}.sbd-cancel__refund-card-line span:first-child{color:#64748b;display:inline-flex;align-items:center}.sbd-cancel__refund-card-line--neg{color:#64748b;font-weight:600;font-variant-numeric:tabular-nums}.sbd-cancel__refund-card-total{display:flex;align-items:center;justify-content:space-between}.sbd-cancel__refund-card-total span{font-size:14.5px;font-weight:700;color:#1e2e4a}.sbd-cancel__refund-card-total strong{font-size:17px;font-weight:800;color:#1e2e4a;font-variant-numeric:tabular-nums}.sbd-cancel__btn-keep{flex:1 1 0}.sbd-cancel__btn-primary{flex:1 1 0;background:#6d4aff!important;border-color:#6d4aff!important;color:#fff!important}.sbd-cancel__btn-primary:hover:not(:disabled){background:#5c3ce6!important;border-color:#5c3ce6!important}.sbd-cancel__btn-primary:disabled{opacity:.5}.sbd-cancel__paid{display:flex;align-items:center;justify-content:space-between;margin:0 -20px;padding:14px 20px;border-radius:0;background:#f1f0fb;border-top:1px solid #e7e5f7;border-bottom:1px solid #e7e5f7}.sbd-cancel__paid span{font-size:13.5px;font-weight:600;color:#4b4670}.sbd-cancel__paid strong{font-size:18px;font-weight:800;color:#2b2747;font-variant-numeric:tabular-nums;letter-spacing:.01em}.sbd-cancel__group{display:flex;flex-direction:column}.sbd-cancel__group-label{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.07em;margin-bottom:2px}.sbd-cancel__group-label--deduct{color:#e05656}.sbd-cancel__group-label--refund{color:#16a34a}.sbd-cancel__line{display:flex;justify-content:space-between;align-items:center;padding:11px 0;font-size:13.5px;border-bottom:1px dashed #e5e7eb}.sbd-cancel__line:last-child{border-bottom:none}.sbd-cancel__line-label{display:inline-flex;align-items:center;color:#334155}.sbd-cancel__line-val{font-weight:700;font-variant-numeric:tabular-nums}.sbd-cancel__line-val--neg{color:#dc2626}.sbd-cancel__line-val--pos{color:#16a34a}.sbd-cancel__tag{margin-left:8px;font-size:10px;font-weight:700;line-height:1;padding:3px 8px;border-radius:6px;background:#ede9fe;color:#6d28d9;letter-spacing:.02em}.sbd-cancel__total-deduct{display:flex;align-items:center;justify-content:space-between;padding:13px 16px;border-radius:10px;background:#fdf2f2;border:1px solid #f6c9c9}.sbd-cancel__total-deduct span{font-size:14px;font-weight:700;color:#b3344a}.sbd-cancel__total-deduct strong{font-size:15px;font-weight:800;color:#dc2626;font-variant-numeric:tabular-nums}.sbd-cancel__calc{display:flex;align-items:center;gap:12px;margin:2px 0;color:#94a3b8;font-size:13px;font-variant-numeric:tabular-nums}.sbd-cancel__calc:before,.sbd-cancel__calc:after{content:\"\";flex:1;height:1px;background:#e5e7eb}.sbd-cancel__refund-total{display:flex;align-items:center;justify-content:space-between}.sbd-cancel__refund-total span{font-size:16px;font-weight:800;color:#1e2e4a}.sbd-cancel__refund-total strong{font-size:26px;font-weight:800;color:#16a34a;font-variant-numeric:tabular-nums;letter-spacing:.01em}.sbd-cancel__formula{text-align:center;padding:14px 16px;border-radius:10px;background:#1f2438;color:#f1f5f9;font-family:SFMono-Regular,ui-monospace,Menlo,Consolas,monospace;font-size:14px;font-weight:600;letter-spacing:.04em;font-variant-numeric:tabular-nums}.sbd-cancel__discrepancy{display:flex;align-items:flex-start;gap:10px;padding:11px 13px;border-radius:8px;font-size:12.5px;line-height:1.45;background:#fffbeb;border:1px solid #fde68a;color:#92400e}.sbd-cancel__discrepancy i{flex-shrink:0;margin-top:1px;font-size:14px;color:#d97706}.sbd-cancel__body{padding:16px 20px;display:flex;flex-direction:column;gap:12px}.sbd-cancel__leg-card{width:100%;border-radius:12px;border:1px solid #e2e8f0;background:#fff;overflow:hidden}.sbd-cancel__leg-card-header{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:14px 16px;cursor:pointer}.sbd-cancel__leg-checkbox{display:flex;align-items:center;gap:10px;font-size:13.5px;font-weight:700;color:#1e2e4a;cursor:pointer}.sbd-cancel__leg-checkbox input[type=checkbox]{width:17px;height:17px;accent-color:#6d4aff;cursor:pointer}.sbd-cancel__leg-chevron{flex-shrink:0;width:26px;height:26px;display:flex;align-items:center;justify-content:center;border:none;background:transparent;color:#64748b;cursor:pointer;transition:transform .15s}.sbd-cancel__leg-chevron i{font-size:13px}.sbd-cancel__leg-chevron--collapsed{transform:rotate(180deg)}.sbd-cancel__leg-card-body{display:flex;flex-direction:column;gap:4px;padding:0 16px 16px}.sbd-cancel__leg-card-body--excluded{opacity:.5}.sbd-cancel__notice{display:flex;align-items:flex-start;gap:10px;padding:10px 12px;border-radius:8px;font-size:12.5px}.sbd-cancel__notice i{flex-shrink:0;margin-top:1px;font-size:14px}.sbd-cancel__notice p{margin:2px 0 0;font-size:12px;opacity:.8}.sbd-cancel__notice strong{display:block;font-weight:700}.sbd-cancel__notice--warning{background:#fff7ed;border:1px solid #fed7aa;color:#9a3412}.sbd-cancel__notice--warning i{color:#f97316}.sbd-cancel__notice--info{background:#eff6ff;border:1px solid #bfdbfe;color:#1e40af}.sbd-cancel__notice--info i{color:#3b82f6}.sbd-cancel__notice--neutral{background:#f7f7f8;border:1px solid #e5e7eb;color:#111827}.sbd-cancel__notice--neutral i{color:#111827}.sbd-cancel__notice--tip{background:#f0fdf4;border:1px solid #bbf7d0;color:#14532d}.sbd-cancel__notice--tip i{color:#22c55e}.sbd-cancel__notice--success{background:#f0fdf4;border:1px solid #bbf7d0;color:#15803c}.sbd-cancel__notice--success i{color:#16a34a}.sbd-cancel__notice--credit{background:#faf5ff;border:1px solid #e9d5ff;color:#5b21b6;font-size:12px}.sbd-cancel__notice--credit i{color:#a855f7}.sbd-cancel__notice--shield{background:#eff6ff;border:1px solid #bfdbfe;color:#1e40af}.sbd-cancel__notice--shield strong{display:inline;font-weight:700}.sbd-cancel__notice--shield i{color:#3b82f6}.sbd-cancel__source-selector{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:4px 0}.sbd-cancel__source-label{font-size:11px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap;margin-right:2px}.sbd-cancel__source-chip{font-size:12px;font-weight:600;padding:4px 14px;border-radius:20px;border:1.5px solid #e2e8f0;background:#f8fafc;color:#475569;cursor:pointer;transition:border-color .15s,color .15s,background .15s;display:inline-flex;align-items:center}.sbd-cancel__source-chip:hover{border-color:#94a3b8;color:#475569;background:#f1f5f9}.sbd-cancel__source-chip--active{background:#eff6ff;color:#1d4ed8;border-color:#93c5fd}.sbd-cancel__source-chip--active:hover{background:#dbeafe;color:#1d4ed8;border-color:#93c5fd}.sbd-cancel__source-chip--no-refund{border-color:#fecaca;color:#b91c1c;background:#fff5f5}.sbd-cancel__source-chip--no-refund:hover{border-color:#fca5a5;background:#fee2e2;color:#b91c1c}.sbd-cancel__source-chip--active.sbd-cancel__source-chip--no-refund{background:#fee2e2;color:#991b1b;border-color:#fca5a5}.sbd-cancel__source-chip--active.sbd-cancel__source-chip--no-refund:hover{background:#fecaca}.sbd-cancel__footer{display:flex;justify-content:flex-end;gap:10px;align-items:stretch;padding:14px 20px;border-top:1px solid #e2e8f0;background:#fff}.sbd-cancel__btn-danger-pill{flex:1 1 0;border-radius:999px!important;background:#fee2e2!important;border-color:#fee2e2!important;color:#dc2626!important;font-weight:700!important}.sbd-cancel__btn-danger-pill:hover:not(:disabled){background:#fecaca!important;border-color:#fecaca!important}.sbd-cancel__btn-danger-pill:disabled{opacity:.5}.sbd-cancel__method-header{display:flex;align-items:flex-start;gap:12px;padding:20px 20px 0}.sbd-cancel__method-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-cancel__method-close .pi{font-size:13px}.sbd-cancel__method-close:hover{background:#e2e8f0}.sbd-cancel__method-header-text{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px;text-align:center;margin-right:32px}.sbd-cancel__method-title{font-size:19px;font-weight:800;color:#1e2e4a;margin:0}.sbd-cancel__method-sub{font-size:12.5px;color:#94a3b8}.sbd-cancel__method-body{padding:16px 20px 20px;display:flex;flex-direction:column;gap:14px}.sbd-cancel__select-leg-body{padding:16px 20px 20px;display:flex;flex-direction:column;gap:12px}.sbd-cancel__leg-select-card{position:relative;border:1.5px solid #e2e8f0;border-radius:16px;padding:16px 16px 16px 48px;transition:border-color .15s}.sbd-cancel__leg-select-card--selected:not(.sbd-cancel__leg-select-card--frozen){border-color:#6d4aff}.sbd-cancel__leg-select-card--disabled{opacity:.7}.sbd-cancel__leg-select-card--frozen{opacity:.6;border-color:#e2e8f0}.sbd-cancel__leg-select-lock{position:absolute;top:16px;left:16px;width:22px;height:22px;display:flex;align-items:center;justify-content:center;font-size:14px;color:#94a3b8}.sbd-cancel__single-leg-note{display:flex;align-items:flex-start;gap:10px;padding:12px 14px;border-radius:12px;background:#eaf2ff;border:1px solid #b9d6ff;color:#1a5fd6;font-size:13px;line-height:1.5}.sbd-cancel__single-leg-note i{flex-shrink:0;margin-top:1px}.sbd-cancel__all-non-refundable-note{display:flex;align-items:flex-start;gap:10px;padding:12px 14px;border-radius:12px;background:#fdf3e3;border:1px solid #f3d9a4;color:#8a5a1e;font-size:13px;line-height:1.5}.sbd-cancel__all-non-refundable-note i{flex-shrink:0;margin-top:1px;color:#b98a3e}.sbd-cancel__leg-select-checkbox{position:absolute;top:16px;left:16px;width:22px;height:22px;cursor:pointer}.sbd-cancel__leg-select-checkbox input{position:absolute;inset:0;opacity:0;margin:0;cursor:pointer}.sbd-cancel__leg-select-checkbox input:disabled{cursor:not-allowed}.sbd-cancel__leg-select-icon{display:flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:6px;border:1.5px solid #cbd5e1;background:#fff;color:#fff;font-size:12px}input:checked+.sbd-cancel__leg-select-icon{background:#6d4aff;border-color:#6d4aff}input:disabled+.sbd-cancel__leg-select-icon{background:#f1f5f9;border-color:#e2e8f0}.sbd-cancel__leg-select-content{display:flex;flex-direction:column;gap:8px}.sbd-cancel__leg-select-top{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}.sbd-cancel__leg-select-name{font-size:15px;font-weight:700;color:#1e2e4a}.sbd-cancel__leg-select-date{font-size:13px;color:#64748b;margin-top:2px}.sbd-cancel__leg-select-times{display:flex;align-items:center;gap:8px}.sbd-cancel__leg-select-time{font-size:18px;font-weight:700;color:#1e2e4a;font-variant-numeric:tabular-nums}.sbd-cancel__leg-select-line{flex:1;position:relative;display:flex;flex-direction:column;align-items:center;gap:2px;padding:0 16px 0 8px}.sbd-cancel__leg-select-line:before{content:\"\";position:absolute;left:0;right:14px;top:50%;height:2px;background:#cbd5e1;z-index:1}.sbd-cancel__leg-select-line-stops{font-size:12px;color:#64748b}.sbd-cancel__leg-select-line-dur{font-size:11px;color:#94a3b8}.sbd-cancel__leg-select-line-arrow{position:absolute;right:4px;top:51%;transform:translateY(-50%);font-size:11px;color:#64748b}.sbd-cancel__leg-select-footer{display:flex;align-items:center;gap:8px}.sbd-cancel__leg-select-airline{font-size:12.5px;color:#64748b}.sbd-cancel__leg-select-operated-by{font-size:12px;color:#94a3b8}.sbd-cancel__leg-select-chevron{font-size:12px;color:#94a3b8;margin-inline-start:auto}.sbd-cancel__leg-select-hint{text-align:center;padding:8px 20px 0;font-size:13px;color:#94a3b8}.sbd-cancel__leg-select-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-cancel__leg-select-notice i{flex-shrink:0;margin-top:1px;color:#b98a3e}.sbd-cancel__method-amount{display:flex;align-items:center;justify-content:space-between}.sbd-cancel__method-amount span{font-size:14px;color:#1e2e4a}.sbd-cancel__method-amount strong{font-size:22px;font-weight:800;color:#1e2e4a;font-variant-numeric:tabular-nums}.sbd-cancel__method-option{display:flex;align-items:flex-start;gap:12px;width:100%;text-align:left;padding:16px;border-radius:16px;border:1.5px solid #e2e8f0;background:#fff;cursor:pointer;transition:border-color .15s,box-shadow .15s}.sbd-cancel__method-option--active{border-color:#6d4aff;box-shadow:0 0 0 1px #6d4aff}.sbd-cancel__method-option:disabled{opacity:.5;cursor:not-allowed}.sbd-cancel__method-icon{flex-shrink:0;width:44px;height:44px;border-radius:10px;display:flex;align-items:center;justify-content:center;border:1px solid #e2e8f0;color:#1e2e4a}.sbd-cancel__method-icon .pi{font-size:16px}.sbd-cancel__method-body-text{flex:1;display:flex;flex-direction:column;align-items:flex-start;gap:4px}.sbd-cancel__method-name{font-size:16px;font-weight:700;color:#1e2e4a;display:inline-flex;align-items:center;gap:6px}.sbd-cancel__method-desc{font-size:13px;color:#64748b}.sbd-cancel__method-badge{display:inline-flex;align-items:center;gap:5px;margin-top:4px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.03em;padding:4px 10px;border-radius:20px}.sbd-cancel__method-badge svg{flex-shrink:0}.sbd-cancel__method-badge--instant{background:#dcfce7;color:#166534}.sbd-cancel__method-badge--days,.sbd-cancel__method-badge--loyalty{background:#eff6ff;color:#1e40af}.sbd-cancel__option-end{flex-shrink:0;display:flex;align-items:center;gap:8px}.sbd-cancel__method-radio{flex-shrink:0;width:18px;height:18px;margin-top:2px;border-radius:50%;border:2px solid #7c3aed;position:relative}.sbd-cancel__method-option--active .sbd-cancel__method-radio{border-color:#6d4aff}.sbd-cancel__method-option--active .sbd-cancel__method-radio:after{content:\"\";position:absolute;inset:3px;border-radius:50%;background:#6d4aff}.sbd-cancel__method-option--split{flex-wrap:wrap}.sbd-cancel__method-split-total{flex-shrink:0;font-size:16px;font-weight:700;color:#1e2e4a;font-variant-numeric:tabular-nums}.sbd-cancel__method-split-breakdown{flex-basis:100%;display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;margin-top:12px;padding-top:12px;border-top:1px solid #e2e8f0}.sbd-cancel__method-split-row{flex:1 1 auto;display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%;font-size:14px;color:#1e2e4a}.sbd-cancel__method-split-row strong{font-weight:700;font-variant-numeric:tabular-nums}.sbd-cancel__method-split-label{color:#64748b;display:inline-flex;align-items:center;gap:6px}.sbd-cancel__mada-logo{display:inline-flex;align-items:center;flex-shrink:0}.sbd-cancel__mada-logo svg{display:block}.sbd-cancel__result-hero{display:flex;flex-direction:column;align-items:center;gap:18px;padding:28px 0 8px}.sbd-cancel__result-icon{width:84px;height:84px;border-radius:50%;display:flex;align-items:center;justify-content:center}.sbd-cancel__result-icon .pi{font-size:30px;color:#fff}.sbd-cancel__result-icon--green{background:#4ade80}.sbd-cancel__result-icon--orange{background:#fb923c}.sbd-cancel__result-title{font-size:19px;font-weight:800;color:#1e2e4a;margin:0;text-align:center}.sbd-cancel__result-row{display:flex;align-items:flex-start;gap:12px;width:100%;padding:12px 14px;border-radius:14px;background:#f8fafc}.sbd-cancel__result-body-text{flex:1;min-width:0;display:flex;flex-direction:column;align-items:flex-start;gap:5px}.sbd-cancel__result-body-text .sbd-cancel__method-badge{text-transform:none;letter-spacing:normal;line-height:1.35;white-space:normal}.sbd-cancel__result-sub-rows{display:flex;flex-direction:column;gap:2px;width:100%}.sbd-cancel__result-sub-row{display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;font-size:12.5px;color:#64748b}.sbd-cancel__result-sub-row strong{font-weight:700;color:#64748b;font-variant-numeric:tabular-nums}.sbd-cancel__result-amount{font-size:16px;font-weight:800;color:#1e2e4a;font-variant-numeric:tabular-nums;white-space:nowrap}.sbd-cancel__result-overlay{position:absolute;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;padding:24px}.sbd-cancel__result-backdrop{position:absolute;inset:0;background:#0000008c}.sbd-cancel__result-modal{position:relative;width:100%;max-width:400px;max-height:90%;overflow-y:auto;display:flex;flex-direction:column;gap:16px;padding:24px 20px;border-radius:20px;background:#fff;box-shadow:0 24px 60px #00000059}.sbd-cancel__result-done{width:100%}\n"] }]
2583
+ }], ctorParameters: () => [] });
2584
+
2585
+ class SbdFlightItineraryCardComponent {
2586
+ constructor() {
2587
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2588
+ this.data = input(null);
2589
+ this.product = input.required();
2590
+ this.verified = input(false);
2591
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2592
+ this.bi = computed(() => this.data()?.data?.booking_info);
2593
+ this.legs = computed(() => {
2594
+ const d = this.bi();
2595
+ if (!d)
2596
+ return [];
2597
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2598
+ return (d.leg_info ?? []).map((leg, i) => ({
2599
+ direction: leg.LegDirectionality,
2600
+ airlineName: (leg.LegAirlinesNames ?? []).join(', '),
2601
+ cabin: leg.LegCabins?.[0]?.Name ?? '',
2602
+ fareBrand: leg.fareBrand?.name ?? '',
2603
+ from: leg.LegDepartureAirportCode,
2604
+ to: leg.LegArrivalAirportCode,
2605
+ depTime: leg.LegDepartureDateTime,
2606
+ arrTime: leg.LegArrivalDateTime,
2607
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2608
+ stops: (leg.Stops ?? []),
2609
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2610
+ segments: (d.airline_info?.[i]?.Segments ?? []),
2611
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2612
+ durationSegments: (d.duration?.legs?.[i]?.Segments ?? []),
2613
+ }));
2614
+ });
2615
+ this.passengers = computed(() =>
2616
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2617
+ (this.bi()?.passenger_info?.passengers ?? []));
2618
+ this.payment = computed(() => this.bi()?.payment_status);
2619
+ this.typeLabel = { ADT: 'Adult', CNN: 'Child', INF: 'Infant' };
2620
+ }
2621
+ fmtTime(dt) {
2622
+ if (!dt)
2623
+ return '—';
2624
+ try {
2625
+ return new Date(dt).toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });
2626
+ }
2627
+ catch {
2628
+ return dt;
2629
+ }
2630
+ }
2631
+ fmtShortDate(dt) {
2632
+ if (!dt)
2633
+ return '—';
2634
+ try {
2635
+ return new Date(dt).toLocaleDateString('en-GB', { day: '2-digit', month: 'short' });
2636
+ }
2637
+ catch {
2638
+ return dt;
2639
+ }
2640
+ }
2641
+ fmtMins(mins) {
2642
+ if (mins == null)
2643
+ return '—';
2644
+ return `${Math.floor(mins / 60)}h ${mins % 60}m`;
2645
+ }
2646
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdFlightItineraryCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2647
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SbdFlightItineraryCardComponent, isStandalone: true, selector: "app-sbd-flight-itinerary-card", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, product: { classPropertyName: "product", publicName: "product", isSignal: true, isRequired: true, transformFunction: null }, verified: { classPropertyName: "verified", publicName: "verified", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"fic\">\n @if (product() === 'flight') {\n\n <!-- \u2500\u2500 Verified pill \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (verified()) {\n <div class=\"fic__verified-bar\">\n <span class=\"fic__verified-pill\">\n <i class=\"pi pi-verified\"></i> Verified with Airlines\n </span>\n </div>\n }\n\n <!-- \u2500\u2500 Legs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @for (leg of legs(); track leg.direction + $index; let li = $index) {\n <div class=\"fic__leg\" [class.fic__leg--return]=\"leg.direction === 'Return'\">\n\n <!-- Leg header -->\n <div class=\"fic__leg-header\">\n <div class=\"fic__leg-direction\">\n <i class=\"pi\" [class.pi-arrow-right]=\"leg.direction !== 'Return'\" [class.pi-arrow-left]=\"leg.direction === 'Return'\"></i>\n <span>{{ leg.direction }}</span>\n </div>\n <div class=\"fic__leg-chips\">\n @if (leg.cabin) {\n <span class=\"fic__chip fic__chip--cabin\">{{ leg.cabin }}</span>\n }\n @if (leg.fareBrand) {\n <span class=\"fic__chip fic__chip--fare\">{{ leg.fareBrand }}</span>\n }\n </div>\n </div>\n\n <!-- Airline row -->\n @if (leg.airlineName) {\n <div class=\"fic__airline\">\n <i class=\"pi pi-send\"></i>\n <span>{{ leg.airlineName }}</span>\n </div>\n }\n\n <!-- Route timeline -->\n <div class=\"fic__route\">\n\n <!-- Origin -->\n <div class=\"fic__airport\">\n <div class=\"fic__airport-code\">{{ leg.from }}</div>\n <div class=\"fic__airport-time\">{{ fmtTime(leg.depTime) }}</div>\n <div class=\"fic__airport-date\">{{ fmtShortDate(leg.depTime) }}</div>\n </div>\n\n <!-- Segments + stops -->\n <div class=\"fic__timeline\">\n\n <!-- Top row: all labels -->\n <div class=\"fic__tl-top\">\n @for (seg of leg.durationSegments; track seg.DepartureAirportCode; let si = $index) {\n <div class=\"fic__tl-seg\">\n <span class=\"fic__tl-flt\">{{ leg.segments[si]?.AirlineCode }}{{ leg.segments[si]?.FlightNumber }}</span>\n <span class=\"fic__tl-dur\">{{ fmtMins(seg.SegmentElapsedTimeMinutes) }}</span>\n </div>\n @if (si < leg.stops.length) {\n <div class=\"fic__tl-stop\">\n <span class=\"fic__stop-dot\"></span>\n <span class=\"fic__stop-code\">{{ leg.stops[si].StopAirport }}</span>\n <span class=\"fic__stop-sep\">\u00B7</span>\n <span class=\"fic__stop-dur\">{{ fmtMins(leg.stops[si].LayoverDuration) }}</span>\n </div>\n }\n }\n </div>\n\n <!-- Bottom row: single continuous line + one arrow -->\n <div class=\"fic__tl-track\">\n <div class=\"fic__tl-line\"></div>\n <i class=\"pi pi-send fic__tl-plane\"></i>\n </div>\n\n </div>\n\n <!-- Destination -->\n <div class=\"fic__airport fic__airport--right\">\n <div class=\"fic__airport-code\">{{ leg.to }}</div>\n <div class=\"fic__airport-time\">{{ fmtTime(leg.arrTime) }}</div>\n <div class=\"fic__airport-date\">{{ fmtShortDate(leg.arrTime) }}</div>\n </div>\n\n </div>\n </div>\n }\n\n <!-- \u2500\u2500 Passengers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (passengers().length > 0) {\n <div class=\"fic__section\">\n <div class=\"fic__section-label\">\n <i class=\"pi pi-users\"></i> Passengers\n <span class=\"fic__section-count\">{{ passengers().length }}</span>\n </div>\n <div class=\"fic__pax-list\">\n @for (p of passengers(); track p.passengerId) {\n <div class=\"fic__pax\">\n <div class=\"fic__pax-top\">\n <span class=\"fic__pax-badge fic__pax-badge--{{ p.type | lowercase }}\">{{ typeLabel[p.type] ?? p.type }}</span>\n <span class=\"fic__pax-name\">{{ p.name?.prefix }} {{ p.name?.givenName }} {{ p.name?.surName }}</span>\n </div>\n <div class=\"fic__pax-bottom\">\n @if (p.identity?.number) {\n <span class=\"fic__pax-doc\"><i class=\"pi pi-id-card\"></i> {{ p.identity?.number }}</span>\n }\n @if (p.email) {\n <span class=\"fic__pax-email\">{{ p.email }}</span>\n }\n </div>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- \u2500\u2500 Payment \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (payment()) {\n <div class=\"fic__payment\">\n <div class=\"fic__payment-main\">\n <i class=\"pi pi-credit-card fic__payment-icon\"></i>\n <span class=\"fic__payment-amount\">{{ payment()?.amount }} {{ payment()?.currency }}</span>\n <span class=\"fic__payment-status\" [class.fic__payment-status--ok]=\"payment()?.status?.key === 'SUCCESS'\">\n <i class=\"pi\" [class.pi-check-circle]=\"payment()?.status?.key === 'SUCCESS'\" [class.pi-clock]=\"payment()?.status?.key !== 'SUCCESS'\"></i>\n {{ payment()?.status?.name }}\n </span>\n </div>\n @if (payment()?.method || payment()?.date) {\n <div class=\"fic__payment-sub\">\n @if (payment()?.method) { <span>{{ payment()?.method }}</span> }\n @if (payment()?.method && payment()?.date) { <span class=\"fic__dot\">\u00B7</span> }\n @if (payment()?.date) { <span>{{ payment()?.date }}</span> }\n </div>\n }\n </div>\n }\n\n\n }\n</div>\n", styles: [".fic{background:#fff;border:1px solid #e2e8f0;border-radius:12px;overflow:hidden;font-family:inherit}.fic__leg{padding:16px 16px 18px;border-bottom:1px solid #e2e8f0}.fic__leg--return{background:#fafbff}.fic__leg-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.fic__leg-direction{display:flex;align-items:center;gap:6px;font-size:11px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:#1e2e4a}.fic__leg-direction .pi{font-size:10px;color:#475569}.fic__leg-chips{display:flex;gap:5px}.fic__chip{font-size:10.5px;font-weight:600;padding:2px 8px;border-radius:12px;letter-spacing:.02em}.fic__chip--cabin{background:#eff6ff;color:#1d4ed8}.fic__chip--fare{background:#fef9c3;color:#854d0e}.fic__airline{display:flex;align-items:center;gap:5px;font-size:12px;color:#64748b;margin-bottom:14px}.fic__airline .pi{font-size:11px;color:#94a3b8;transform:rotate(45deg)}.fic__route{display:flex;align-items:flex-start;gap:0}.fic__airport{display:flex;flex-direction:column;align-items:flex-start;flex-shrink:0}.fic__airport--right{align-items:flex-end}.fic__airport-code{font-size:26px;font-weight:800;color:#1e2e4a;line-height:1}.fic__airport-time{font-size:13px;font-weight:600;color:#334155;margin-top:3px}.fic__airport-date{font-size:10.5px;color:#94a3b8;margin-top:1px}.fic__timeline{flex:1;display:flex;flex-direction:column;gap:4px;margin:0 4px}.fic__tl-top{display:flex;align-items:flex-end;gap:0}.fic__tl-seg{flex:1;display:flex;flex-direction:column;align-items:center;gap:1px}.fic__tl-flt{font-size:10px;font-weight:700;color:#1e2e4a;background:#f1f5f9;padding:1px 5px;border-radius:4px;white-space:nowrap}.fic__tl-dur{font-size:9.5px;color:#94a3b8;white-space:nowrap}.fic__tl-stop{flex-shrink:0;display:inline-flex;align-items:center;gap:4px;padding:0 8px 1px}.fic__tl-track{display:flex;align-items:center;height:22px}.fic__tl-line{flex:1;height:2px;background:#cbd5e1}.fic__tl-plane{font-size:13px;color:#475569;transform:rotate(45deg);flex-shrink:0;margin-left:-2px}.fic__stop-dot{width:7px;height:7px;border-radius:50%;background:#94a3b8;flex-shrink:0}.fic__stop-code{font-size:11px;font-weight:700;color:#334155}.fic__stop-sep{font-size:11px;color:#cbd5e1}.fic__stop-dur{font-size:11px;font-weight:600;color:#f59e0b}.fic__section{padding:14px 16px;border-bottom:1px solid #e2e8f0}.fic__section-label{display:flex;align-items:center;gap:5px;font-size:10.5px;font-weight:800;letter-spacing:.07em;text-transform:uppercase;color:#64748b;margin-bottom:10px}.fic__section-label .pi{font-size:11px}.fic__section-count{margin-left:auto;background:#f1f5f9;color:#475569;font-size:10px;font-weight:700;padding:1px 6px;border-radius:10px}.fic__pax-list{display:flex;flex-direction:column;gap:8px}.fic__pax{background:#f8fafc;border-radius:8px;padding:8px 10px;display:flex;flex-direction:column;gap:5px}.fic__pax-top{display:flex;align-items:center;gap:7px}.fic__pax-badge{font-size:9.5px;font-weight:700;letter-spacing:.04em;padding:2px 7px;border-radius:10px;flex-shrink:0;text-transform:uppercase}.fic__pax-badge--adt{background:#dbeafe;color:#1d4ed8}.fic__pax-badge--cnn{background:#dcfce7;color:#15803d}.fic__pax-badge--inf{background:#fce7f3;color:#9d174d}.fic__pax-name{font-size:12.5px;font-weight:600;color:#1e2e4a;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.fic__pax-bottom{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.fic__pax-doc{display:inline-flex;align-items:center;gap:3px;font-size:11px;font-family:monospace;color:#475569;background:#f1f5f9;padding:1px 6px;border-radius:4px}.fic__pax-doc .pi{font-size:10px}.fic__pax-email{font-size:11px;color:#94a3b8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:180px}.fic__payment{padding:12px 16px;background:#f8fafc;display:flex;flex-direction:column;gap:4px}.fic__payment-main{display:flex;align-items:center;gap:8px}.fic__payment-icon{font-size:14px;color:#64748b}.fic__payment-amount{font-size:14px;font-weight:700;color:#1e2e4a}.fic__payment-status{display:flex;align-items:center;gap:4px;font-size:12px;font-weight:600;color:#64748b;margin-left:auto}.fic__payment-status .pi{font-size:12px}.fic__payment-status--ok{color:#16a34a}.fic__payment-sub{display:flex;align-items:center;gap:6px;font-size:11.5px;color:#94a3b8;padding-left:22px}.fic__dot{color:#cbd5e1}.fic__verified-bar{display:flex;align-items:center;padding:8px 12px;background:#f0fdf4;border-bottom:1px solid #bbf7d0}.fic__verified-pill{display:inline-flex;align-items:center;gap:5px;padding:3px 10px 3px 7px;background:#16a34a;color:#fff;font-size:11px;font-weight:700;border-radius:20px;letter-spacing:.02em}.fic__verified-pill .pi{font-size:12px}\n"], dependencies: [{ kind: "ngmodule", type: TagModule }, { kind: "pipe", type: LowerCasePipe, name: "lowercase" }] }); }
2648
+ }
2649
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdFlightItineraryCardComponent, decorators: [{
2650
+ type: Component,
2651
+ args: [{ selector: 'app-sbd-flight-itinerary-card', standalone: true, imports: [TagModule, UpperCasePipe, LowerCasePipe], template: "<div class=\"fic\">\n @if (product() === 'flight') {\n\n <!-- \u2500\u2500 Verified pill \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (verified()) {\n <div class=\"fic__verified-bar\">\n <span class=\"fic__verified-pill\">\n <i class=\"pi pi-verified\"></i> Verified with Airlines\n </span>\n </div>\n }\n\n <!-- \u2500\u2500 Legs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @for (leg of legs(); track leg.direction + $index; let li = $index) {\n <div class=\"fic__leg\" [class.fic__leg--return]=\"leg.direction === 'Return'\">\n\n <!-- Leg header -->\n <div class=\"fic__leg-header\">\n <div class=\"fic__leg-direction\">\n <i class=\"pi\" [class.pi-arrow-right]=\"leg.direction !== 'Return'\" [class.pi-arrow-left]=\"leg.direction === 'Return'\"></i>\n <span>{{ leg.direction }}</span>\n </div>\n <div class=\"fic__leg-chips\">\n @if (leg.cabin) {\n <span class=\"fic__chip fic__chip--cabin\">{{ leg.cabin }}</span>\n }\n @if (leg.fareBrand) {\n <span class=\"fic__chip fic__chip--fare\">{{ leg.fareBrand }}</span>\n }\n </div>\n </div>\n\n <!-- Airline row -->\n @if (leg.airlineName) {\n <div class=\"fic__airline\">\n <i class=\"pi pi-send\"></i>\n <span>{{ leg.airlineName }}</span>\n </div>\n }\n\n <!-- Route timeline -->\n <div class=\"fic__route\">\n\n <!-- Origin -->\n <div class=\"fic__airport\">\n <div class=\"fic__airport-code\">{{ leg.from }}</div>\n <div class=\"fic__airport-time\">{{ fmtTime(leg.depTime) }}</div>\n <div class=\"fic__airport-date\">{{ fmtShortDate(leg.depTime) }}</div>\n </div>\n\n <!-- Segments + stops -->\n <div class=\"fic__timeline\">\n\n <!-- Top row: all labels -->\n <div class=\"fic__tl-top\">\n @for (seg of leg.durationSegments; track seg.DepartureAirportCode; let si = $index) {\n <div class=\"fic__tl-seg\">\n <span class=\"fic__tl-flt\">{{ leg.segments[si]?.AirlineCode }}{{ leg.segments[si]?.FlightNumber }}</span>\n <span class=\"fic__tl-dur\">{{ fmtMins(seg.SegmentElapsedTimeMinutes) }}</span>\n </div>\n @if (si < leg.stops.length) {\n <div class=\"fic__tl-stop\">\n <span class=\"fic__stop-dot\"></span>\n <span class=\"fic__stop-code\">{{ leg.stops[si].StopAirport }}</span>\n <span class=\"fic__stop-sep\">\u00B7</span>\n <span class=\"fic__stop-dur\">{{ fmtMins(leg.stops[si].LayoverDuration) }}</span>\n </div>\n }\n }\n </div>\n\n <!-- Bottom row: single continuous line + one arrow -->\n <div class=\"fic__tl-track\">\n <div class=\"fic__tl-line\"></div>\n <i class=\"pi pi-send fic__tl-plane\"></i>\n </div>\n\n </div>\n\n <!-- Destination -->\n <div class=\"fic__airport fic__airport--right\">\n <div class=\"fic__airport-code\">{{ leg.to }}</div>\n <div class=\"fic__airport-time\">{{ fmtTime(leg.arrTime) }}</div>\n <div class=\"fic__airport-date\">{{ fmtShortDate(leg.arrTime) }}</div>\n </div>\n\n </div>\n </div>\n }\n\n <!-- \u2500\u2500 Passengers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (passengers().length > 0) {\n <div class=\"fic__section\">\n <div class=\"fic__section-label\">\n <i class=\"pi pi-users\"></i> Passengers\n <span class=\"fic__section-count\">{{ passengers().length }}</span>\n </div>\n <div class=\"fic__pax-list\">\n @for (p of passengers(); track p.passengerId) {\n <div class=\"fic__pax\">\n <div class=\"fic__pax-top\">\n <span class=\"fic__pax-badge fic__pax-badge--{{ p.type | lowercase }}\">{{ typeLabel[p.type] ?? p.type }}</span>\n <span class=\"fic__pax-name\">{{ p.name?.prefix }} {{ p.name?.givenName }} {{ p.name?.surName }}</span>\n </div>\n <div class=\"fic__pax-bottom\">\n @if (p.identity?.number) {\n <span class=\"fic__pax-doc\"><i class=\"pi pi-id-card\"></i> {{ p.identity?.number }}</span>\n }\n @if (p.email) {\n <span class=\"fic__pax-email\">{{ p.email }}</span>\n }\n </div>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- \u2500\u2500 Payment \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (payment()) {\n <div class=\"fic__payment\">\n <div class=\"fic__payment-main\">\n <i class=\"pi pi-credit-card fic__payment-icon\"></i>\n <span class=\"fic__payment-amount\">{{ payment()?.amount }} {{ payment()?.currency }}</span>\n <span class=\"fic__payment-status\" [class.fic__payment-status--ok]=\"payment()?.status?.key === 'SUCCESS'\">\n <i class=\"pi\" [class.pi-check-circle]=\"payment()?.status?.key === 'SUCCESS'\" [class.pi-clock]=\"payment()?.status?.key !== 'SUCCESS'\"></i>\n {{ payment()?.status?.name }}\n </span>\n </div>\n @if (payment()?.method || payment()?.date) {\n <div class=\"fic__payment-sub\">\n @if (payment()?.method) { <span>{{ payment()?.method }}</span> }\n @if (payment()?.method && payment()?.date) { <span class=\"fic__dot\">\u00B7</span> }\n @if (payment()?.date) { <span>{{ payment()?.date }}</span> }\n </div>\n }\n </div>\n }\n\n\n }\n</div>\n", styles: [".fic{background:#fff;border:1px solid #e2e8f0;border-radius:12px;overflow:hidden;font-family:inherit}.fic__leg{padding:16px 16px 18px;border-bottom:1px solid #e2e8f0}.fic__leg--return{background:#fafbff}.fic__leg-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.fic__leg-direction{display:flex;align-items:center;gap:6px;font-size:11px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:#1e2e4a}.fic__leg-direction .pi{font-size:10px;color:#475569}.fic__leg-chips{display:flex;gap:5px}.fic__chip{font-size:10.5px;font-weight:600;padding:2px 8px;border-radius:12px;letter-spacing:.02em}.fic__chip--cabin{background:#eff6ff;color:#1d4ed8}.fic__chip--fare{background:#fef9c3;color:#854d0e}.fic__airline{display:flex;align-items:center;gap:5px;font-size:12px;color:#64748b;margin-bottom:14px}.fic__airline .pi{font-size:11px;color:#94a3b8;transform:rotate(45deg)}.fic__route{display:flex;align-items:flex-start;gap:0}.fic__airport{display:flex;flex-direction:column;align-items:flex-start;flex-shrink:0}.fic__airport--right{align-items:flex-end}.fic__airport-code{font-size:26px;font-weight:800;color:#1e2e4a;line-height:1}.fic__airport-time{font-size:13px;font-weight:600;color:#334155;margin-top:3px}.fic__airport-date{font-size:10.5px;color:#94a3b8;margin-top:1px}.fic__timeline{flex:1;display:flex;flex-direction:column;gap:4px;margin:0 4px}.fic__tl-top{display:flex;align-items:flex-end;gap:0}.fic__tl-seg{flex:1;display:flex;flex-direction:column;align-items:center;gap:1px}.fic__tl-flt{font-size:10px;font-weight:700;color:#1e2e4a;background:#f1f5f9;padding:1px 5px;border-radius:4px;white-space:nowrap}.fic__tl-dur{font-size:9.5px;color:#94a3b8;white-space:nowrap}.fic__tl-stop{flex-shrink:0;display:inline-flex;align-items:center;gap:4px;padding:0 8px 1px}.fic__tl-track{display:flex;align-items:center;height:22px}.fic__tl-line{flex:1;height:2px;background:#cbd5e1}.fic__tl-plane{font-size:13px;color:#475569;transform:rotate(45deg);flex-shrink:0;margin-left:-2px}.fic__stop-dot{width:7px;height:7px;border-radius:50%;background:#94a3b8;flex-shrink:0}.fic__stop-code{font-size:11px;font-weight:700;color:#334155}.fic__stop-sep{font-size:11px;color:#cbd5e1}.fic__stop-dur{font-size:11px;font-weight:600;color:#f59e0b}.fic__section{padding:14px 16px;border-bottom:1px solid #e2e8f0}.fic__section-label{display:flex;align-items:center;gap:5px;font-size:10.5px;font-weight:800;letter-spacing:.07em;text-transform:uppercase;color:#64748b;margin-bottom:10px}.fic__section-label .pi{font-size:11px}.fic__section-count{margin-left:auto;background:#f1f5f9;color:#475569;font-size:10px;font-weight:700;padding:1px 6px;border-radius:10px}.fic__pax-list{display:flex;flex-direction:column;gap:8px}.fic__pax{background:#f8fafc;border-radius:8px;padding:8px 10px;display:flex;flex-direction:column;gap:5px}.fic__pax-top{display:flex;align-items:center;gap:7px}.fic__pax-badge{font-size:9.5px;font-weight:700;letter-spacing:.04em;padding:2px 7px;border-radius:10px;flex-shrink:0;text-transform:uppercase}.fic__pax-badge--adt{background:#dbeafe;color:#1d4ed8}.fic__pax-badge--cnn{background:#dcfce7;color:#15803d}.fic__pax-badge--inf{background:#fce7f3;color:#9d174d}.fic__pax-name{font-size:12.5px;font-weight:600;color:#1e2e4a;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.fic__pax-bottom{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.fic__pax-doc{display:inline-flex;align-items:center;gap:3px;font-size:11px;font-family:monospace;color:#475569;background:#f1f5f9;padding:1px 6px;border-radius:4px}.fic__pax-doc .pi{font-size:10px}.fic__pax-email{font-size:11px;color:#94a3b8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:180px}.fic__payment{padding:12px 16px;background:#f8fafc;display:flex;flex-direction:column;gap:4px}.fic__payment-main{display:flex;align-items:center;gap:8px}.fic__payment-icon{font-size:14px;color:#64748b}.fic__payment-amount{font-size:14px;font-weight:700;color:#1e2e4a}.fic__payment-status{display:flex;align-items:center;gap:4px;font-size:12px;font-weight:600;color:#64748b;margin-left:auto}.fic__payment-status .pi{font-size:12px}.fic__payment-status--ok{color:#16a34a}.fic__payment-sub{display:flex;align-items:center;gap:6px;font-size:11.5px;color:#94a3b8;padding-left:22px}.fic__dot{color:#cbd5e1}.fic__verified-bar{display:flex;align-items:center;padding:8px 12px;background:#f0fdf4;border-bottom:1px solid #bbf7d0}.fic__verified-pill{display:inline-flex;align-items:center;gap:5px;padding:3px 10px 3px 7px;background:#16a34a;color:#fff;font-size:11px;font-weight:700;border-radius:20px;letter-spacing:.02em}.fic__verified-pill .pi{font-size:12px}\n"] }]
2652
+ }] });
2653
+
2654
+ class SbdDataSearchComponent {
2655
+ constructor() {
2656
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2657
+ this.data = input(null);
2658
+ this.query = signal('');
2659
+ this.allEntries = computed(() => {
2660
+ const d = this.data()?.data?.booking_info;
2661
+ if (!d)
2662
+ return [];
2663
+ return this.flatten(d);
2664
+ });
2665
+ this.results = computed(() => {
2666
+ const q = this.query().trim().toLowerCase();
2667
+ if (q.length < 2)
2668
+ return [];
2669
+ return this.allEntries()
2670
+ .filter(e => e.key.toLowerCase().includes(q) ||
2671
+ e.rawValue.toLowerCase().includes(q) ||
2672
+ e.displayValue.toLowerCase().includes(q))
2673
+ .slice(0, 50);
2674
+ });
2675
+ this.totalCount = computed(() => {
2676
+ const q = this.query().trim().toLowerCase();
2677
+ if (q.length < 2)
2678
+ return 0;
2679
+ return this.allEntries().filter(e => e.key.toLowerCase().includes(q) ||
2680
+ e.rawValue.toLowerCase().includes(q) ||
2681
+ e.displayValue.toLowerCase().includes(q)).length;
2682
+ });
2683
+ }
2684
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2685
+ flatten(obj, pathParts = []) {
2686
+ if (obj === null || obj === undefined)
2687
+ return [];
2688
+ if (typeof obj !== 'object') {
2689
+ const raw = String(obj);
2690
+ const key = pathParts[pathParts.length - 1] ?? '';
2691
+ const { display, type } = this.formatEntry(key, raw);
2692
+ return [{ path: pathParts.slice(0, -1).join(' › '), key, rawValue: raw, displayValue: display, type }];
2693
+ }
2694
+ if (Array.isArray(obj)) {
2695
+ return obj.flatMap((item, i) => this.flatten(item, [...pathParts, `[${i}]`]));
2696
+ }
2697
+ return Object.entries(obj).flatMap(([k, v]) => this.flatten(v, [...pathParts, k]));
2698
+ }
2699
+ formatEntry(key, raw) {
2700
+ if (/minutes|duration/i.test(key)) {
2701
+ const n = Number(raw);
2702
+ if (!isNaN(n) && raw !== '') {
2703
+ return { display: `${Math.floor(n / 60)}h ${n % 60}m`, type: 'duration' };
2704
+ }
2705
+ }
2706
+ if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(raw)) {
2707
+ try {
2708
+ return { display: new Date(raw).toLocaleString('en-GB', { day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' }), type: 'date' };
2709
+ }
2710
+ catch { /* fall through */ }
2711
+ }
2712
+ if (/^\d{4}-\d{2}-\d{2}$/.test(raw)) {
2713
+ try {
2714
+ return { display: new Date(raw + 'T00:00:00').toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }), type: 'date' };
2715
+ }
2716
+ catch { /* fall through */ }
2717
+ }
2718
+ if (raw === 'true' || raw === 'false')
2719
+ return { display: raw, type: 'bool' };
2720
+ if (raw !== '' && !isNaN(Number(raw)))
2721
+ return { display: raw, type: 'number' };
2722
+ return { display: raw, type: 'text' };
2723
+ }
2724
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdDataSearchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2725
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SbdDataSearchComponent, isStandalone: true, selector: "app-sbd-data-search", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"sbd-data-search\">\n <div class=\"sbd-data-search__input-wrap\">\n <i class=\"pi pi-search sbd-data-search__icon\"></i>\n <input pInputText class=\"sbd-data-search__input\"\n placeholder=\"Search any field or value from this booking\u2026\"\n [ngModel]=\"query()\" (ngModelChange)=\"query.set($event)\" />\n @if (query().length >= 2) {\n <span class=\"sbd-data-search__count\">{{ totalCount() }} match{{ totalCount() !== 1 ? 'es' : '' }}</span>\n }\n </div>\n\n @if (query().length >= 2 && results().length > 0) {\n <div class=\"sbd-data-search__results\">\n @for (entry of results(); track entry.path + entry.key) {\n <div class=\"sbd-data-search__result\">\n <div class=\"sbd-data-search__result-left\">\n <div class=\"sbd-data-search__result-path\">\n @if (entry.path) { <span class=\"sbd-data-search__result-pathtext\">{{ entry.path }} \u203A</span> }\n <span class=\"sbd-data-search__result-key\">{{ entry.key }}</span>\n </div>\n </div>\n <div class=\"sbd-data-search__result-right\">\n <span class=\"sbd-data-search__type-badge sbd-data-search__type-badge--{{ entry.type }}\">{{ entry.type }}</span>\n <span class=\"sbd-data-search__result-value\">{{ entry.displayValue }}</span>\n </div>\n </div>\n }\n @if (totalCount() > 50) {\n <div class=\"sbd-data-search__more\">+{{ totalCount() - 50 }} more \u2014 refine your search</div>\n }\n </div>\n }\n\n @if (query().length >= 2 && results().length === 0) {\n <div class=\"sbd-data-search__empty\">\n <i class=\"pi pi-inbox\"></i> No fields or values match \"{{ query() }}\"\n </div>\n }\n</div>\n", styles: [".sbd-data-search{background:#fff;border:1px solid #e2e8f0;border-radius:10px;overflow:hidden}.sbd-data-search__input-wrap{display:flex;align-items:center;gap:10px;padding:12px 16px;border-bottom:1px solid transparent;transition:border-color .15s}.sbd-data-search__input-wrap:focus-within{border-bottom-color:#e2e8f0}.sbd-data-search__icon{color:#94a3b8;font-size:15px;flex-shrink:0}.sbd-data-search__input{flex:1;border:none!important;outline:none!important;box-shadow:none!important;padding:0!important;font-size:14px;background:transparent!important;color:#1e2e4a}.sbd-data-search__input::placeholder{color:#94a3b8}.sbd-data-search__count{font-size:12px;font-weight:600;color:#6366f1;background:#eef2ff;padding:2px 8px;border-radius:10px;white-space:nowrap}.sbd-data-search__results{max-height:320px;overflow-y:auto}.sbd-data-search__result{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 16px;border-bottom:1px solid #f1f5f9;cursor:default;transition:background .1s}.sbd-data-search__result:last-child{border-bottom:none}.sbd-data-search__result:hover{background:#f8fafc}.sbd-data-search__result-left{min-width:0;flex:1;overflow:hidden}.sbd-data-search__result-right{display:flex;align-items:center;gap:8px;flex-shrink:0;max-width:55%}.sbd-data-search__result-path{font-size:11px;color:#94a3b8;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sbd-data-search__result-pathtext{color:#cbd5e1}.sbd-data-search__result-key{font-weight:600;color:#475569}.sbd-data-search__result-value{font-size:13px;font-weight:600;color:#1e2e4a;max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:monospace}.sbd-data-search__type-badge{font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;padding:2px 6px;border-radius:4px;white-space:nowrap;flex-shrink:0}.sbd-data-search__type-badge--date{background:#eff6ff;color:#3b82f6}.sbd-data-search__type-badge--duration{background:#fff7ed;color:#f97316}.sbd-data-search__type-badge--bool{background:#f0fdf4;color:#22c55e}.sbd-data-search__type-badge--number{background:#faf5ff;color:#a855f7}.sbd-data-search__type-badge--text{background:#f8fafc;color:#64748b}.sbd-data-search__more{padding:10px 16px;font-size:12px;color:#94a3b8;text-align:center;font-style:italic}.sbd-data-search__empty{display:flex;align-items:center;gap:8px;padding:16px;font-size:13px;color:#94a3b8}.sbd-data-search__empty .pi{font-size:16px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.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: InputTextModule }, { kind: "directive", type: i3.InputText, selector: "[pInputText]" }] }); }
2726
+ }
2727
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdDataSearchComponent, decorators: [{
2728
+ type: Component,
2729
+ args: [{ selector: 'app-sbd-data-search', standalone: true, imports: [FormsModule, InputTextModule], template: "<div class=\"sbd-data-search\">\n <div class=\"sbd-data-search__input-wrap\">\n <i class=\"pi pi-search sbd-data-search__icon\"></i>\n <input pInputText class=\"sbd-data-search__input\"\n placeholder=\"Search any field or value from this booking\u2026\"\n [ngModel]=\"query()\" (ngModelChange)=\"query.set($event)\" />\n @if (query().length >= 2) {\n <span class=\"sbd-data-search__count\">{{ totalCount() }} match{{ totalCount() !== 1 ? 'es' : '' }}</span>\n }\n </div>\n\n @if (query().length >= 2 && results().length > 0) {\n <div class=\"sbd-data-search__results\">\n @for (entry of results(); track entry.path + entry.key) {\n <div class=\"sbd-data-search__result\">\n <div class=\"sbd-data-search__result-left\">\n <div class=\"sbd-data-search__result-path\">\n @if (entry.path) { <span class=\"sbd-data-search__result-pathtext\">{{ entry.path }} \u203A</span> }\n <span class=\"sbd-data-search__result-key\">{{ entry.key }}</span>\n </div>\n </div>\n <div class=\"sbd-data-search__result-right\">\n <span class=\"sbd-data-search__type-badge sbd-data-search__type-badge--{{ entry.type }}\">{{ entry.type }}</span>\n <span class=\"sbd-data-search__result-value\">{{ entry.displayValue }}</span>\n </div>\n </div>\n }\n @if (totalCount() > 50) {\n <div class=\"sbd-data-search__more\">+{{ totalCount() - 50 }} more \u2014 refine your search</div>\n }\n </div>\n }\n\n @if (query().length >= 2 && results().length === 0) {\n <div class=\"sbd-data-search__empty\">\n <i class=\"pi pi-inbox\"></i> No fields or values match \"{{ query() }}\"\n </div>\n }\n</div>\n", styles: [".sbd-data-search{background:#fff;border:1px solid #e2e8f0;border-radius:10px;overflow:hidden}.sbd-data-search__input-wrap{display:flex;align-items:center;gap:10px;padding:12px 16px;border-bottom:1px solid transparent;transition:border-color .15s}.sbd-data-search__input-wrap:focus-within{border-bottom-color:#e2e8f0}.sbd-data-search__icon{color:#94a3b8;font-size:15px;flex-shrink:0}.sbd-data-search__input{flex:1;border:none!important;outline:none!important;box-shadow:none!important;padding:0!important;font-size:14px;background:transparent!important;color:#1e2e4a}.sbd-data-search__input::placeholder{color:#94a3b8}.sbd-data-search__count{font-size:12px;font-weight:600;color:#6366f1;background:#eef2ff;padding:2px 8px;border-radius:10px;white-space:nowrap}.sbd-data-search__results{max-height:320px;overflow-y:auto}.sbd-data-search__result{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 16px;border-bottom:1px solid #f1f5f9;cursor:default;transition:background .1s}.sbd-data-search__result:last-child{border-bottom:none}.sbd-data-search__result:hover{background:#f8fafc}.sbd-data-search__result-left{min-width:0;flex:1;overflow:hidden}.sbd-data-search__result-right{display:flex;align-items:center;gap:8px;flex-shrink:0;max-width:55%}.sbd-data-search__result-path{font-size:11px;color:#94a3b8;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sbd-data-search__result-pathtext{color:#cbd5e1}.sbd-data-search__result-key{font-weight:600;color:#475569}.sbd-data-search__result-value{font-size:13px;font-weight:600;color:#1e2e4a;max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:monospace}.sbd-data-search__type-badge{font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;padding:2px 6px;border-radius:4px;white-space:nowrap;flex-shrink:0}.sbd-data-search__type-badge--date{background:#eff6ff;color:#3b82f6}.sbd-data-search__type-badge--duration{background:#fff7ed;color:#f97316}.sbd-data-search__type-badge--bool{background:#f0fdf4;color:#22c55e}.sbd-data-search__type-badge--number{background:#faf5ff;color:#a855f7}.sbd-data-search__type-badge--text{background:#f8fafc;color:#64748b}.sbd-data-search__more{padding:10px 16px;font-size:12px;color:#94a3b8;text-align:center;font-style:italic}.sbd-data-search__empty{display:flex;align-items:center;gap:8px;padding:16px;font-size:13px;color:#94a3b8}.sbd-data-search__empty .pi{font-size:16px}\n"] }]
2730
+ }] });
2731
+
2732
+ const AR_LABELS = {
2733
+ 'PNR': 'رقم الحجز',
2734
+ 'Booking ID': 'معرف الحجز',
2735
+ 'Supplier ref': 'مرجع المورد',
2736
+ 'Supplier': 'المورد',
2737
+ 'Cross-seller': 'حجز متقاطع',
2738
+ 'Departure': 'المغادرة',
2739
+ 'Arrival': 'الوصول',
2740
+ 'Duration': 'المدة',
2741
+ 'Direction': 'الاتجاه',
2742
+ 'Departure time': 'وقت المغادرة',
2743
+ 'Arrival time': 'وقت الوصول',
2744
+ 'Stops': 'التوقفات',
2745
+ 'Journey duration': 'مدة الرحلة',
2746
+ 'Airline(s)': 'شركة الطيران',
2747
+ 'Flight no.': 'رقم الرحلة',
2748
+ 'Flight': 'الرحلة',
2749
+ 'IATA code': 'رمز IATA',
2750
+ 'Aircraft': 'نوع الطائرة',
2751
+ 'Operating code': 'رمز التشغيل',
2752
+ 'Route': 'المسار',
2753
+ 'Cabin': 'درجة المقصورة',
2754
+ 'Fare brand': 'نوع التعرفة',
2755
+ 'Fare desc.': 'وصف التعرفة',
2756
+ 'Departure airport': 'مطار المغادرة',
2757
+ 'Dep. terminal': 'صالة المغادرة',
2758
+ 'Arrival airport': 'مطار الوصول',
2759
+ 'Arr. terminal': 'صالة الوصول',
2760
+ 'Type': 'النوع',
2761
+ 'Gender': 'الجنس',
2762
+ 'Date of birth': 'تاريخ الميلاد',
2763
+ 'Passport': 'جواز السفر',
2764
+ 'Nationality': 'الجنسية',
2765
+ 'Passport exp.': 'انتهاء الجواز',
2766
+ 'Issue country': 'بلد الإصدار',
2767
+ 'Doc type': 'نوع الوثيقة',
2768
+ 'Email': 'البريد الإلكتروني',
2769
+ 'Phone': 'الهاتف',
2770
+ 'Total': 'الإجمالي',
2771
+ 'Adults (ADT)': 'بالغون',
2772
+ 'Children (CNN)': 'أطفال',
2773
+ 'Infants (INF)': 'رضع',
2774
+ 'Status': 'الحالة',
2775
+ 'Amount': 'المبلغ',
2776
+ 'Paid amount': 'المبلغ المدفوع',
2777
+ 'Payment method': 'طريقة الدفع',
2778
+ 'Date': 'التاريخ',
2779
+ 'Currency': 'العملة',
2780
+ 'Transaction ref': 'مرجع المعاملة',
2781
+ 'Amount paid': 'المبلغ المدفوع',
2782
+ 'Source': 'المصدر',
2783
+ 'Total paid': 'إجمالي المدفوع',
2784
+ 'Adult · Cabin': 'بالغ · حقيبة مقصورة',
2785
+ 'Adult · Checked': 'بالغ · حقيبة مسجلة',
2786
+ 'Child · Cabin': 'طفل · حقيبة مقصورة',
2787
+ 'Child · Checked': 'طفل · حقيبة مسجلة',
2788
+ 'Infant · Cabin': 'رضيع · حقيبة مقصورة',
2789
+ 'Infant · Checked': 'رضيع · حقيبة مسجلة',
2790
+ 'Checked': 'حقيبة مسجلة',
2791
+ 'Original amount': 'المبلغ الأصلي',
2792
+ 'Cancellation fee': 'رسوم الإلغاء',
2793
+ 'Total refund': 'إجمالي الاسترداد',
2794
+ 'Refundable': 'قابل للاسترداد',
2795
+ 'Cancel fee': 'رسوم الإلغاء',
2796
+ 'Deadline': 'الموعد النهائي',
2797
+ 'Policy': 'السياسة',
2798
+ 'Penalty': 'الغرامة',
2799
+ 'Non-refundable charge': 'رسوم غير قابلة للاسترداد',
2800
+ 'Non-refundable ancillary': 'خدمات إضافية غير قابلة للاسترداد',
2801
+ 'Gift deduction': 'خصم الهدية',
2802
+ 'Coupon deduction': 'خصم القسيمة',
2803
+ 'Loyalty deduction': 'خصم نقاط الولاء',
2804
+ 'Card refund': 'استرداد البطاقة',
2805
+ 'Credit refund': 'استرداد الرصيد',
2806
+ 'Wallet refund': 'استرداد المحفظة',
2807
+ 'Loyalty points refund': 'استرداد نقاط الولاء',
2808
+ 'Leg total refund': 'إجمالي استرداد هذا الاتجاه',
2809
+ 'Room type': 'نوع الغرفة',
2810
+ 'Beds': 'الأسرّة',
2811
+ 'View': 'الإطلالة',
2812
+ 'Floor': 'الطابق',
2813
+ 'Size': 'المساحة',
2814
+ 'Check-in date': 'تاريخ الوصول',
2815
+ 'Check-in time': 'وقت الوصول',
2816
+ 'Check-out date': 'تاريخ المغادرة',
2817
+ 'Check-out time': 'وقت المغادرة',
2818
+ 'Nights': 'عدد الليالي',
2819
+ 'Hotel': 'الفندق',
2820
+ 'Stars': 'النجوم',
2821
+ 'Address': 'العنوان',
2822
+ 'City': 'المدينة',
2823
+ 'Country': 'الدولة',
2824
+ 'Zip code': 'الرمز البريدي',
2825
+ 'Landmark': 'معلم قريب',
2826
+ 'Balance': 'الرصيد',
2827
+ 'Cashback': 'كاش باك',
2828
+ 'Promo credit': 'رصيد ترويجي',
2829
+ 'Last updated': 'آخر تحديث',
2830
+ 'Board basis': 'أساس الإقامة',
2831
+ 'Breakfast': 'الإفطار',
2832
+ 'Lunch': 'الغداء',
2833
+ 'Dinner': 'العشاء',
2834
+ };
2835
+ const AR_SECTION_TITLES = {
2836
+ 'Booking Reference': 'مرجع الحجز',
2837
+ 'Payment': 'الدفع',
2838
+ 'Summary': 'ملخص',
2839
+ 'Transaction': 'المعاملة',
2840
+ 'References': 'المراجع',
2841
+ 'Total': 'الإجمالي',
2842
+ 'Info': 'معلومات',
2843
+ 'Status': 'الحالة',
2844
+ 'Add-on Baggage': 'أمتعة إضافية',
2845
+ 'Change History': 'سجل التغييرات',
2846
+ };
2847
+
2848
+ class SbdResultPanelComponent {
2849
+ constructor() {
2850
+ this.toast = inject(SbdToastService);
2851
+ this.actionLabel = input('');
2852
+ this.elapsedMs = input(0);
2853
+ this.status = input('idle');
2854
+ this.result = input(null);
2855
+ this.feedbackGiven = output();
2856
+ }
2857
+ get elapsedSeconds() { return (this.elapsedMs() / 1000).toFixed(2); }
2858
+ copy() {
2859
+ const r = this.result();
2860
+ if (!r)
2861
+ return;
2862
+ const lines = [r.title, r.subtitle ?? '', '', ...r.points.map(p => `${p.label}: ${p.value}`), r.callout ? `\nNote: ${r.callout.message}` : ''].filter(Boolean);
2863
+ navigator.clipboard.writeText(lines.join('\n')).then(() => this.toast.success('Copied to clipboard'));
2864
+ }
2865
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdResultPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2866
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SbdResultPanelComponent, isStandalone: true, selector: "app-sbd-result-panel", inputs: { actionLabel: { classPropertyName: "actionLabel", publicName: "actionLabel", isSignal: true, isRequired: false, transformFunction: null }, elapsedMs: { classPropertyName: "elapsedMs", publicName: "elapsedMs", isSignal: true, isRequired: false, transformFunction: null }, status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, result: { classPropertyName: "result", publicName: "result", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { feedbackGiven: "feedbackGiven" }, ngImport: i0, template: "<div class=\"sbd-result-panel\">\n @if (status() === 'loading') {\n <div class=\"sbd-result-panel__loading\">\n <div class=\"sbd-result-panel__loading-header\">\n <p-skeleton width=\"200px\" height=\"18px\" /><p-skeleton width=\"80px\" height=\"18px\" />\n </div>\n <p-skeleton width=\"60%\" height=\"28px\" styleClass=\"mt-3 mb-2\" />\n <p-skeleton width=\"40%\" height=\"16px\" styleClass=\"mb-4\" />\n <div class=\"sbd-result-panel__loading-points\">\n @for (i of [1,2,3,4]; track i) {\n <div class=\"sbd-result-panel__loading-row\">\n <p-skeleton width=\"100px\" height=\"13px\" /><p-skeleton width=\"160px\" height=\"13px\" />\n </div>\n }\n </div>\n </div>\n }\n @if (status() === 'success' && result()) {\n <div @fadeInUp>\n <div class=\"sbd-result-panel__header\">\n <div class=\"sbd-result-panel__header-left\">\n <i class=\"pi pi-check-circle sbd-result-panel__check-icon\"></i>\n <span class=\"sbd-result-panel__header-label\">Result \u00B7 {{ actionLabel() }}</span>\n </div>\n <span class=\"sbd-result-panel__elapsed\">Found in {{ elapsedSeconds }}s</span>\n </div>\n <div class=\"sbd-result-panel__body\">\n <h2 class=\"sbd-result-panel__title\">{{ result()!.title }}</h2>\n @if (result()!.subtitle) { <p class=\"sbd-result-panel__subtitle\">{{ result()!.subtitle }}</p> }\n\n <!-- Sectioned layout (refund breakdown etc.) -->\n @if (result()!.sections?.length) {\n <div class=\"sbd-result-panel__sections\">\n @for (section of result()!.sections!; track section.title) {\n <div class=\"sbd-result-panel__section\">\n <div class=\"sbd-result-panel__section-title\">{{ section.title }}</div>\n @for (row of section.rows; track row.label) {\n @if (row.value) {\n <div class=\"sbd-result-panel__section-row\">\n <span class=\"sbd-result-panel__section-row-label\">{{ row.label }}</span>\n <span class=\"sbd-result-panel__section-row-value sbd-result-panel__section-row-value--{{ row.valueClass ?? 'neutral' }}\">{{ row.value }}</span>\n </div>\n } @else {\n <div class=\"sbd-result-panel__section-divider\">{{ row.label }}</div>\n }\n }\n </div>\n }\n </div>\n }\n\n <!-- Flat points layout (all other actions) -->\n @else if (result()!.points?.length) {\n <ul class=\"sbd-result-panel__points\">\n @for (point of result()!.points!; track point.label) {\n <li class=\"sbd-result-panel__point\">\n <span class=\"sbd-result-panel__point-label\">{{ point.label }}</span>\n <span class=\"sbd-result-panel__point-value\">{{ point.value }}</span>\n </li>\n }\n </ul>\n }\n\n @if (result()!.callout) {\n <div class=\"sbd-result-panel__callout sbd-result-panel__callout--{{ result()!.callout!.type }}\">\n <i class=\"pi pi-info-circle\"></i><span>{{ result()!.callout!.message }}</span>\n </div>\n }\n </div>\n <div class=\"sbd-result-panel__footer\">\n <div class=\"sbd-result-panel__feedback\">\n <span class=\"sbd-result-panel__feedback-label\">Helpful?</span>\n <button class=\"sbd-result-panel__feedback-btn sbd-result-panel__feedback-btn--up\" (click)=\"feedbackGiven.emit('up')\">\n <i class=\"pi pi-thumbs-up\"></i>\n </button>\n <button class=\"sbd-result-panel__feedback-btn sbd-result-panel__feedback-btn--down\" (click)=\"feedbackGiven.emit('down')\">\n <i class=\"pi pi-thumbs-down\"></i>\n </button>\n </div>\n </div>\n </div>\n }\n @if (status() === 'error') {\n <div class=\"sbd-result-panel__error\" @fadeInUp>\n <i class=\"pi pi-exclamation-triangle\"></i><span>Could not load this information. Please try again.</span>\n </div>\n }\n @if (status() === 'idle') {\n <div class=\"sbd-result-panel__idle\">\n <i class=\"pi pi-arrow-up-left\"></i><span>Select an action above to see results here</span>\n </div>\n }\n</div>\n", styles: [".sbd-result-panel{background:#fff;border-radius:12px;border:1px solid #e2e8f0;overflow:hidden}.sbd-result-panel__loading{padding:20px}.sbd-result-panel__loading-header{display:flex;justify-content:space-between;margin-bottom:14px}.sbd-result-panel__loading-points{display:flex;flex-direction:column;gap:10px}.sbd-result-panel__loading-row{display:flex;gap:24px}.sbd-result-panel__header{display:flex;align-items:center;justify-content:space-between;padding:12px 20px;background:#00b67a12;border-bottom:1px solid rgba(0,182,122,.15)}.sbd-result-panel__header-left{display:flex;align-items:center;gap:8px}.sbd-result-panel__check-icon{color:#00b67a;font-size:15px}.sbd-result-panel__header-label{font-size:13px;font-weight:600;color:#1e2e4a}.sbd-result-panel__elapsed{font-size:11.5px;font-weight:600;color:#00b67a;background:#00b67a1a;border-radius:20px;padding:2px 10px}.sbd-result-panel__body{padding:20px}.sbd-result-panel__title{font-size:20px;font-weight:800;color:#1e2e4a;margin:0 0 4px}.sbd-result-panel__subtitle{font-size:13px;color:#94a3b8;margin:0 0 14px}.sbd-result-panel__points{list-style:none;margin:0 0 14px;padding:0;display:flex;flex-direction:column}.sbd-result-panel__point{display:flex;align-items:baseline;gap:14px;padding:9px 0;border-bottom:1px solid #f1f5f9}.sbd-result-panel__point:last-child{border-bottom:none}.sbd-result-panel__point:before{content:\"\";width:6px;height:6px;border-radius:50%;background:#3b82f6;flex-shrink:0;margin-top:5px;align-self:flex-start}.sbd-result-panel__point-label{font-size:12px;color:#94a3b8;font-weight:500;width:140px;flex-shrink:0}.sbd-result-panel__point-value{font-size:13px;font-weight:600;color:#1e2e4a;flex:1}.sbd-result-panel__sections{display:flex;flex-direction:column;gap:14px;margin-bottom:14px}.sbd-result-panel__section{border:1px solid #e2e8f0;border-radius:8px;overflow:hidden}.sbd-result-panel__section-title{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#64748b;background:#f8fafc;padding:7px 12px;border-bottom:1px solid #e2e8f0}.sbd-result-panel__section-row{display:flex;justify-content:space-between;align-items:center;padding:7px 12px;border-bottom:1px solid #f1f5f9;font-size:13px}.sbd-result-panel__section-row:last-child{border-bottom:none}.sbd-result-panel__section-row-label{color:#64748b;font-size:12.5px}.sbd-result-panel__section-row-value{font-weight:600;font-size:13px}.sbd-result-panel__section-row-value--positive{color:#16a34a}.sbd-result-panel__section-row-value--negative{color:#ef4444}.sbd-result-panel__section-row-value--neutral{color:#1e2e4a}.sbd-result-panel__section-divider{padding:5px 12px;font-size:10.5px;font-weight:700;letter-spacing:.05em;color:#94a3b8;background:#f8fafc;border-bottom:1px solid #f1f5f9;text-align:center}.sbd-result-panel__callout{display:flex;align-items:flex-start;gap:8px;padding:12px;border-radius:8px;font-size:12.5px;font-weight:500;border-left:3px solid #6366f1;background:#f8f9ff;color:#1e2e4a}.sbd-result-panel__callout--warning{border-left-color:#f97316;background:#f973160f}.sbd-result-panel__callout--tip{border-left-color:#0ea5e9;background:#0ea5e912}.sbd-result-panel__callout i{font-size:14px;margin-top:1px}.sbd-result-panel__footer{display:flex;align-items:center;justify-content:flex-end;padding:10px 20px;border-top:1px solid #f1f5f9;background:#f8fafc;gap:10px}.sbd-result-panel__feedback{display:flex;align-items:center;gap:6px}.sbd-result-panel__feedback-label{font-size:12px;color:#94a3b8;margin-right:2px}.sbd-result-panel__feedback-btn{background:none;border:1px solid #e2e8f0;border-radius:8px;width:34px;height:34px;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .15s}.sbd-result-panel__feedback-btn .pi{font-size:14px;color:#94a3b8}.sbd-result-panel__feedback-btn:hover{border-color:#cbd5e1;background:#f1f5f9}.sbd-result-panel__feedback-btn:hover .pi{color:#475569}.sbd-result-panel__feedback-btn--up:hover{border-color:#86efac;background:#f0fdf4}.sbd-result-panel__feedback-btn--up:hover .pi{color:#16a34a}.sbd-result-panel__feedback-btn--down:hover{border-color:#fca5a5;background:#fef2f2}.sbd-result-panel__feedback-btn--down:hover .pi{color:#ef4444}.sbd-result-panel__error{display:flex;align-items:center;gap:8px;padding:24px;color:#ef4444;font-size:13.5px;font-weight:500}.sbd-result-panel__error i{font-size:18px}.sbd-result-panel__idle{display:flex;align-items:center;gap:8px;padding:24px;color:#94a3b8;font-size:13px}.sbd-result-panel__idle i{font-size:16px}\n"], dependencies: [{ kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1$2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "style", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "ngmodule", type: ButtonModule }], animations: [
2867
+ trigger('fadeInUp', [
2868
+ transition(':enter', [
2869
+ style({ opacity: 0, transform: 'translateY(8px)' }),
2870
+ animate('200ms ease-out', style({ opacity: 1, transform: 'translateY(0)' })),
2871
+ ]),
2872
+ ]),
2873
+ ] }); }
2874
+ }
2875
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdResultPanelComponent, decorators: [{
2876
+ type: Component,
2877
+ args: [{ selector: 'app-sbd-result-panel', standalone: true, imports: [SkeletonModule, ButtonModule], animations: [
2878
+ trigger('fadeInUp', [
2879
+ transition(':enter', [
2880
+ style({ opacity: 0, transform: 'translateY(8px)' }),
2881
+ animate('200ms ease-out', style({ opacity: 1, transform: 'translateY(0)' })),
2882
+ ]),
2883
+ ]),
2884
+ ], template: "<div class=\"sbd-result-panel\">\n @if (status() === 'loading') {\n <div class=\"sbd-result-panel__loading\">\n <div class=\"sbd-result-panel__loading-header\">\n <p-skeleton width=\"200px\" height=\"18px\" /><p-skeleton width=\"80px\" height=\"18px\" />\n </div>\n <p-skeleton width=\"60%\" height=\"28px\" styleClass=\"mt-3 mb-2\" />\n <p-skeleton width=\"40%\" height=\"16px\" styleClass=\"mb-4\" />\n <div class=\"sbd-result-panel__loading-points\">\n @for (i of [1,2,3,4]; track i) {\n <div class=\"sbd-result-panel__loading-row\">\n <p-skeleton width=\"100px\" height=\"13px\" /><p-skeleton width=\"160px\" height=\"13px\" />\n </div>\n }\n </div>\n </div>\n }\n @if (status() === 'success' && result()) {\n <div @fadeInUp>\n <div class=\"sbd-result-panel__header\">\n <div class=\"sbd-result-panel__header-left\">\n <i class=\"pi pi-check-circle sbd-result-panel__check-icon\"></i>\n <span class=\"sbd-result-panel__header-label\">Result \u00B7 {{ actionLabel() }}</span>\n </div>\n <span class=\"sbd-result-panel__elapsed\">Found in {{ elapsedSeconds }}s</span>\n </div>\n <div class=\"sbd-result-panel__body\">\n <h2 class=\"sbd-result-panel__title\">{{ result()!.title }}</h2>\n @if (result()!.subtitle) { <p class=\"sbd-result-panel__subtitle\">{{ result()!.subtitle }}</p> }\n\n <!-- Sectioned layout (refund breakdown etc.) -->\n @if (result()!.sections?.length) {\n <div class=\"sbd-result-panel__sections\">\n @for (section of result()!.sections!; track section.title) {\n <div class=\"sbd-result-panel__section\">\n <div class=\"sbd-result-panel__section-title\">{{ section.title }}</div>\n @for (row of section.rows; track row.label) {\n @if (row.value) {\n <div class=\"sbd-result-panel__section-row\">\n <span class=\"sbd-result-panel__section-row-label\">{{ row.label }}</span>\n <span class=\"sbd-result-panel__section-row-value sbd-result-panel__section-row-value--{{ row.valueClass ?? 'neutral' }}\">{{ row.value }}</span>\n </div>\n } @else {\n <div class=\"sbd-result-panel__section-divider\">{{ row.label }}</div>\n }\n }\n </div>\n }\n </div>\n }\n\n <!-- Flat points layout (all other actions) -->\n @else if (result()!.points?.length) {\n <ul class=\"sbd-result-panel__points\">\n @for (point of result()!.points!; track point.label) {\n <li class=\"sbd-result-panel__point\">\n <span class=\"sbd-result-panel__point-label\">{{ point.label }}</span>\n <span class=\"sbd-result-panel__point-value\">{{ point.value }}</span>\n </li>\n }\n </ul>\n }\n\n @if (result()!.callout) {\n <div class=\"sbd-result-panel__callout sbd-result-panel__callout--{{ result()!.callout!.type }}\">\n <i class=\"pi pi-info-circle\"></i><span>{{ result()!.callout!.message }}</span>\n </div>\n }\n </div>\n <div class=\"sbd-result-panel__footer\">\n <div class=\"sbd-result-panel__feedback\">\n <span class=\"sbd-result-panel__feedback-label\">Helpful?</span>\n <button class=\"sbd-result-panel__feedback-btn sbd-result-panel__feedback-btn--up\" (click)=\"feedbackGiven.emit('up')\">\n <i class=\"pi pi-thumbs-up\"></i>\n </button>\n <button class=\"sbd-result-panel__feedback-btn sbd-result-panel__feedback-btn--down\" (click)=\"feedbackGiven.emit('down')\">\n <i class=\"pi pi-thumbs-down\"></i>\n </button>\n </div>\n </div>\n </div>\n }\n @if (status() === 'error') {\n <div class=\"sbd-result-panel__error\" @fadeInUp>\n <i class=\"pi pi-exclamation-triangle\"></i><span>Could not load this information. Please try again.</span>\n </div>\n }\n @if (status() === 'idle') {\n <div class=\"sbd-result-panel__idle\">\n <i class=\"pi pi-arrow-up-left\"></i><span>Select an action above to see results here</span>\n </div>\n }\n</div>\n", styles: [".sbd-result-panel{background:#fff;border-radius:12px;border:1px solid #e2e8f0;overflow:hidden}.sbd-result-panel__loading{padding:20px}.sbd-result-panel__loading-header{display:flex;justify-content:space-between;margin-bottom:14px}.sbd-result-panel__loading-points{display:flex;flex-direction:column;gap:10px}.sbd-result-panel__loading-row{display:flex;gap:24px}.sbd-result-panel__header{display:flex;align-items:center;justify-content:space-between;padding:12px 20px;background:#00b67a12;border-bottom:1px solid rgba(0,182,122,.15)}.sbd-result-panel__header-left{display:flex;align-items:center;gap:8px}.sbd-result-panel__check-icon{color:#00b67a;font-size:15px}.sbd-result-panel__header-label{font-size:13px;font-weight:600;color:#1e2e4a}.sbd-result-panel__elapsed{font-size:11.5px;font-weight:600;color:#00b67a;background:#00b67a1a;border-radius:20px;padding:2px 10px}.sbd-result-panel__body{padding:20px}.sbd-result-panel__title{font-size:20px;font-weight:800;color:#1e2e4a;margin:0 0 4px}.sbd-result-panel__subtitle{font-size:13px;color:#94a3b8;margin:0 0 14px}.sbd-result-panel__points{list-style:none;margin:0 0 14px;padding:0;display:flex;flex-direction:column}.sbd-result-panel__point{display:flex;align-items:baseline;gap:14px;padding:9px 0;border-bottom:1px solid #f1f5f9}.sbd-result-panel__point:last-child{border-bottom:none}.sbd-result-panel__point:before{content:\"\";width:6px;height:6px;border-radius:50%;background:#3b82f6;flex-shrink:0;margin-top:5px;align-self:flex-start}.sbd-result-panel__point-label{font-size:12px;color:#94a3b8;font-weight:500;width:140px;flex-shrink:0}.sbd-result-panel__point-value{font-size:13px;font-weight:600;color:#1e2e4a;flex:1}.sbd-result-panel__sections{display:flex;flex-direction:column;gap:14px;margin-bottom:14px}.sbd-result-panel__section{border:1px solid #e2e8f0;border-radius:8px;overflow:hidden}.sbd-result-panel__section-title{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#64748b;background:#f8fafc;padding:7px 12px;border-bottom:1px solid #e2e8f0}.sbd-result-panel__section-row{display:flex;justify-content:space-between;align-items:center;padding:7px 12px;border-bottom:1px solid #f1f5f9;font-size:13px}.sbd-result-panel__section-row:last-child{border-bottom:none}.sbd-result-panel__section-row-label{color:#64748b;font-size:12.5px}.sbd-result-panel__section-row-value{font-weight:600;font-size:13px}.sbd-result-panel__section-row-value--positive{color:#16a34a}.sbd-result-panel__section-row-value--negative{color:#ef4444}.sbd-result-panel__section-row-value--neutral{color:#1e2e4a}.sbd-result-panel__section-divider{padding:5px 12px;font-size:10.5px;font-weight:700;letter-spacing:.05em;color:#94a3b8;background:#f8fafc;border-bottom:1px solid #f1f5f9;text-align:center}.sbd-result-panel__callout{display:flex;align-items:flex-start;gap:8px;padding:12px;border-radius:8px;font-size:12.5px;font-weight:500;border-left:3px solid #6366f1;background:#f8f9ff;color:#1e2e4a}.sbd-result-panel__callout--warning{border-left-color:#f97316;background:#f973160f}.sbd-result-panel__callout--tip{border-left-color:#0ea5e9;background:#0ea5e912}.sbd-result-panel__callout i{font-size:14px;margin-top:1px}.sbd-result-panel__footer{display:flex;align-items:center;justify-content:flex-end;padding:10px 20px;border-top:1px solid #f1f5f9;background:#f8fafc;gap:10px}.sbd-result-panel__feedback{display:flex;align-items:center;gap:6px}.sbd-result-panel__feedback-label{font-size:12px;color:#94a3b8;margin-right:2px}.sbd-result-panel__feedback-btn{background:none;border:1px solid #e2e8f0;border-radius:8px;width:34px;height:34px;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .15s}.sbd-result-panel__feedback-btn .pi{font-size:14px;color:#94a3b8}.sbd-result-panel__feedback-btn:hover{border-color:#cbd5e1;background:#f1f5f9}.sbd-result-panel__feedback-btn:hover .pi{color:#475569}.sbd-result-panel__feedback-btn--up:hover{border-color:#86efac;background:#f0fdf4}.sbd-result-panel__feedback-btn--up:hover .pi{color:#16a34a}.sbd-result-panel__feedback-btn--down:hover{border-color:#fca5a5;background:#fef2f2}.sbd-result-panel__feedback-btn--down:hover .pi{color:#ef4444}.sbd-result-panel__error{display:flex;align-items:center;gap:8px;padding:24px;color:#ef4444;font-size:13.5px;font-weight:500}.sbd-result-panel__error i{font-size:18px}.sbd-result-panel__idle{display:flex;align-items:center;gap:8px;padding:24px;color:#94a3b8;font-size:13px}.sbd-result-panel__idle i{font-size:16px}\n"] }]
2885
+ }] });
2886
+
2887
+ class SbdActionDialogComponent {
2888
+ constructor() {
2889
+ this.bookingService = inject(SbdBookingService);
2890
+ this.toast = inject(SbdToastService);
2891
+ this.visible = input(false);
2892
+ this.bookingId = input('');
2893
+ this.product = input('flight');
2894
+ this.actions = input([]);
2895
+ this.initialKey = input(null);
2896
+ // Pre-fetched booking data from the parent — avoids a redundant API call
2897
+ // when the action's apiField is already satisfied by the initial lookup.
2898
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2899
+ this.preloadedData = input(null);
2900
+ this.closed = output();
2901
+ this.selectedAction = signal(null);
2902
+ this.actionStatus = signal('idle');
2903
+ this.actionData = signal(null);
2904
+ this.elapsedMs = signal(0);
2905
+ this.copying = signal(false);
2906
+ this.resultContent = computed(() => {
2907
+ const a = this.selectedAction();
2908
+ const d = this.actionData();
2909
+ return a && d ? a.formatter(d) : null;
2910
+ });
2911
+ effect(() => {
2912
+ if (this.visible()) {
2913
+ const key = this.initialKey();
2914
+ const action = key ? this.actions().find(a => a.key === key) ?? null : null;
2915
+ if (action) {
2916
+ this.onActionSelect(action);
2917
+ }
2918
+ }
2919
+ else {
2920
+ this.selectedAction.set(null);
2921
+ this.actionStatus.set('idle');
2922
+ this.actionData.set(null);
2923
+ this.elapsedMs.set(0);
2924
+ }
2925
+ }, { allowSignalWrites: true });
2926
+ }
2927
+ copyResult(mode) {
2928
+ const enResult = this.resultContent();
2929
+ const action = this.selectedAction();
2930
+ if (!enResult || !action)
2931
+ return;
2932
+ if (mode === 'en') {
2933
+ const text = this.buildBilingualText(enResult, null);
2934
+ navigator.clipboard.writeText(text).then(() => this.toast.success('Copied EN'));
2935
+ return;
2936
+ }
2937
+ this.copying.set(true);
2938
+ const arData$ = action.apiField === '$refund_details'
2939
+ ? this.bookingService.getRefundDetails(this.bookingId(), this.product(), 'ar').pipe(map((data) => data), catchError(() => of(null)))
2940
+ : this.bookingService.getBooking(this.bookingId(), this.product(), action.apiField, 'ar').pipe(map(({ data }) => data), catchError(() => of(null)));
2941
+ arData$.subscribe(arRaw => {
2942
+ const arResult = arRaw ? action.formatter(arRaw) : null;
2943
+ const text = mode === 'ar'
2944
+ ? this.buildBilingualText(null, arResult)
2945
+ : this.buildBilingualText(enResult, arResult);
2946
+ const msg = mode === 'ar' ? 'Copied AR' : 'Copied EN + AR';
2947
+ navigator.clipboard.writeText(text)
2948
+ .then(() => this.toast.success(msg))
2949
+ .catch(() => this.toast.success('Copied to clipboard'))
2950
+ .finally(() => this.copying.set(false));
2951
+ });
2952
+ }
2953
+ buildBilingualText(en, ar) {
2954
+ const LTR = '‎';
2955
+ const RTL = '‏';
2956
+ const divider = `${LTR}${'─'.repeat(40)}`;
2957
+ const formatEn = (r) => {
2958
+ const lines = [`${LTR}${r.title}`];
2959
+ if (r.subtitle)
2960
+ lines.push(`${LTR}${r.subtitle}`);
2961
+ lines.push('');
2962
+ if (r.sections?.length) {
2963
+ for (const s of r.sections) {
2964
+ lines.push(`${LTR}── ${s.title} ──`);
2965
+ for (const row of s.rows) {
2966
+ lines.push(row.value ? `${LTR}${row.label}: ${row.value}` : `${LTR}· ${row.label}`);
2967
+ }
2968
+ lines.push('');
2969
+ }
2970
+ }
2971
+ else if (r.points?.length) {
2972
+ for (const p of r.points)
2973
+ lines.push(`${LTR}${p.label}: ${p.value}`);
2974
+ }
2975
+ if (r.callout)
2976
+ lines.push(`${LTR}Note: ${r.callout.message}`);
2977
+ return lines;
2978
+ };
2979
+ const formatAr = (r) => {
2980
+ const tl = (label) => AR_LABELS[label] ?? label;
2981
+ const ts = (title) => AR_SECTION_TITLES[title] ?? title;
2982
+ const lines = [`${RTL}${r.title}`];
2983
+ if (r.subtitle)
2984
+ lines.push(`${RTL}${r.subtitle}`);
2985
+ lines.push('');
2986
+ if (r.sections?.length) {
2987
+ for (const s of r.sections) {
2988
+ lines.push(`${RTL}── ${ts(s.title)} ──`);
2989
+ for (const row of s.rows) {
2990
+ lines.push(row.value ? `${RTL}${tl(row.label)}: ${row.value}` : `${RTL}· ${tl(row.label)}`);
2991
+ }
2992
+ lines.push('');
2993
+ }
2994
+ }
2995
+ else if (r.points?.length) {
2996
+ for (const p of r.points)
2997
+ lines.push(`${RTL}${tl(p.label)}: ${p.value}`);
2998
+ }
2999
+ if (r.callout)
3000
+ lines.push(`${RTL}ملاحظة: ${r.callout.message}`);
3001
+ return lines;
3002
+ };
3003
+ if (!en && ar)
3004
+ return formatAr(ar).join('\r\n');
3005
+ if (en && !ar)
3006
+ return formatEn(en).join('\r\n');
3007
+ return [...formatEn(en), '', divider, '', ...formatAr(ar)].join('\r\n');
3008
+ }
3009
+ onActionSelect(action) {
3010
+ if (!action)
3011
+ return;
3012
+ this.selectedAction.set(action);
3013
+ this.actionStatus.set('loading');
3014
+ this.actionData.set(null);
3015
+ // Use pre-fetched data when available and the action doesn't need its own
3016
+ // endpoint (i.e. not the special $refund_details POST).
3017
+ const cached = this.preloadedData();
3018
+ if (action.apiField !== '$refund_details' && cached) {
3019
+ this.actionData.set(cached);
3020
+ this.elapsedMs.set(0);
3021
+ this.actionStatus.set('success');
3022
+ return;
3023
+ }
3024
+ const start = performance.now();
3025
+ const source$ = action.apiField === '$refund_details'
3026
+ ? this.bookingService.getRefundDetails(this.bookingId(), this.product()).pipe(map((data) => ({ data, elapsedMs: performance.now() - start })))
3027
+ : this.bookingService.getBooking(this.bookingId(), this.product(), action.apiField);
3028
+ source$.subscribe({
3029
+ next: ({ data, elapsedMs }) => {
3030
+ this.actionData.set(data);
3031
+ this.elapsedMs.set(elapsedMs);
3032
+ this.actionStatus.set('success');
3033
+ },
3034
+ error: () => {
3035
+ this.actionStatus.set('error');
3036
+ },
3037
+ });
3038
+ }
3039
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdActionDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3040
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.2.14", type: SbdActionDialogComponent, isStandalone: true, selector: "app-sbd-action-dialog", inputs: { visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, bookingId: { classPropertyName: "bookingId", publicName: "bookingId", isSignal: true, isRequired: false, transformFunction: null }, product: { classPropertyName: "product", publicName: "product", isSignal: true, isRequired: false, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null }, initialKey: { classPropertyName: "initialKey", publicName: "initialKey", isSignal: true, isRequired: false, transformFunction: null }, preloadedData: { classPropertyName: "preloadedData", publicName: "preloadedData", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, ngImport: i0, 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: '720px', maxHeight: '90vh' }\"\n styleClass=\"sbd-action-dialog-wrapper\"\n header=\"Browse Actions\">\n\n <div class=\"sad\">\n\n <!-- ng-select dropdown -->\n <div class=\"sad__picker\">\n <ng-select\n [items]=\"actions()\"\n [ngModel]=\"selectedAction()\"\n (change)=\"onActionSelect($event)\"\n [searchable]=\"true\"\n [clearable]=\"false\"\n bindLabel=\"label\"\n placeholder=\"Search actions...\"\n class=\"sad__select\">\n\n <!-- Selected value display -->\n <ng-template ng-label-tmp let-item=\"item\">\n <div class=\"sad__option\">\n <i class=\"{{ item.icon }} sad__option-icon\"></i>\n <div class=\"sad__option-text\">\n <span class=\"sad__option-label\">{{ item.label }}</span>\n <span class=\"sad__option-sub\">{{ item.subtitle }}</span>\n </div>\n </div>\n </ng-template>\n\n <!-- Dropdown list item -->\n <ng-template ng-option-tmp let-item=\"item\" let-search=\"searchTerm\">\n <div class=\"sad__option\">\n <i class=\"{{ item.icon }} sad__option-icon\"></i>\n <div class=\"sad__option-text\">\n <span class=\"sad__option-label\">{{ item.label }}</span>\n <span class=\"sad__option-sub\">{{ item.subtitle }}</span>\n </div>\n </div>\n </ng-template>\n\n </ng-select>\n </div>\n\n <!-- Result area -->\n <div class=\"sad__result\">\n <app-sbd-result-panel\n [actionLabel]=\"selectedAction()?.label ?? ''\"\n [elapsedMs]=\"elapsedMs()\"\n [status]=\"actionStatus()\"\n [result]=\"resultContent()\"\n />\n </div>\n\n </div>\n\n <ng-template pTemplate=\"footer\">\n <button pButton label=\"Copy EN\" icon=\"pi pi-copy\"\n class=\"p-button-outlined p-button-sm\"\n [disabled]=\"actionStatus() !== 'success' || copying()\"\n (click)=\"copyResult('en')\">\n </button>\n <button pButton label=\"Copy AR\" icon=\"pi pi-copy\"\n class=\"p-button-outlined p-button-sm\"\n [disabled]=\"actionStatus() !== 'success' || copying()\"\n (click)=\"copyResult('ar')\">\n </button>\n <button pButton label=\"Copy EN + AR\"\n [icon]=\"copying() ? 'pi pi-spin pi-spinner' : 'pi pi-copy'\"\n class=\"p-button-outlined p-button-sm\"\n [disabled]=\"actionStatus() !== 'success' || copying()\"\n (click)=\"copyResult('both')\">\n </button>\n <button pButton label=\"Close\" icon=\"pi pi-times\" class=\"p-button-outlined p-button-secondary p-button-sm\" (click)=\"closed.emit()\"></button>\n </ng-template>\n\n</p-dialog>\n", styles: [".sad{display:flex;flex-direction:column;font-family:inherit}.sad__picker{padding:16px 20px;border-bottom:1px solid #e2e8f0}.sad__select{width:100%}.sad__select ::ng-deep .ng-select-container{height:56px}.sad__option{display:flex;align-items:center;gap:10px;padding:2px 0}.sad__option-icon{font-size:14px;color:#64748b;flex-shrink:0;width:20px;text-align:center}.sad__option-text{display:flex;flex-direction:column;gap:2px;min-width:0}.sad__option-label{font-size:13.5px;font-weight:600;color:#1e2e4a}.sad__option-sub{font-size:11.5px;color:#94a3b8}.sad__result{overflow-y:auto;max-height:calc(90vh - 200px)}.sad__result::-webkit-scrollbar{width:4px}.sad__result::-webkit-scrollbar-track{background:transparent}.sad__result::-webkit-scrollbar-thumb{background:#e2e8f0;border-radius:4px}.sad__result ::ng-deep .sbd-result-panel{border:none;border-radius:0;box-shadow:none}.sad__select.ng-select .ng-select-container{border:1px solid #e2e8f0;border-radius:8px;min-height:48px;font-family:inherit;box-shadow:none}.sad__select.ng-select .ng-select-container:hover{border-color:#94a3b8}.sad__select.ng-select .ng-select-container .ng-value-container{padding:0 12px}.sad__select.ng-select .ng-select-container .ng-value-container .ng-placeholder{color:#94a3b8;font-size:13.5px}.sad__select.ng-select .ng-select-container .ng-value-container .ng-value{font-size:13.5px}.sad__select.ng-select .ng-select-container .ng-arrow-wrapper{padding-right:10px}.sad__select.ng-select .ng-select-container .ng-arrow-wrapper .ng-arrow{border-color:#94a3b8 transparent transparent}.sad__select.ng-select.ng-select-focused .ng-select-container{border-color:#94a3b8;box-shadow:0 0 0 3px #94a3b826}.sad__select.ng-select.ng-select-opened .ng-select-container{border-color:#94a3b8;border-radius:8px 8px 0 0}.sad__select.ng-select.ng-select-opened .ng-select-container .ng-arrow{border-color:transparent transparent #94a3b8}.sad__select .ng-dropdown-panel{border:1px solid #e2e8f0;border-top:none;border-radius:0 0 8px 8px;box-shadow:0 8px 24px #00000014}.sad__select .ng-dropdown-panel .ng-dropdown-panel-items{max-height:520px}.sad__select .ng-dropdown-panel .ng-dropdown-panel-items .ng-option{padding:8px 12px;color:#1e2e4a;font-size:13.5px;background:#fff}.sad__select .ng-dropdown-panel .ng-dropdown-panel-items .ng-option:hover,.sad__select .ng-dropdown-panel .ng-dropdown-panel-items .ng-option.ng-option-marked{background:#f8fafc}.sad__select .ng-dropdown-panel .ng-dropdown-panel-items .ng-option.ng-option-selected{background:#f1f5f9;font-weight:500}.sad__select .ng-dropdown-panel .ng-dropdown-panel-items .ng-option.ng-option-selected .sad__option-label{color:#1e2e4a}.sad__select .ng-dropdown-panel .ng-dropdown-panel-items .ng-option.ng-option-selected .sad__option-icon{color:#475569}.sad__select .ng-dropdown-panel .ng-dropdown-header{border-bottom:1px solid #f1f5f9;padding:8px 12px}\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: "directive", type: i2$1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i3$1.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "label", "icon", "loading"] }, { 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]" }, { kind: "component", type: SbdResultPanelComponent, selector: "app-sbd-result-panel", inputs: ["actionLabel", "elapsedMs", "status", "result"], outputs: ["feedbackGiven"] }] }); }
3041
+ }
3042
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdActionDialogComponent, decorators: [{
3043
+ type: Component,
3044
+ args: [{ selector: 'app-sbd-action-dialog', standalone: true, imports: [DialogModule, ButtonModule, FormsModule, NgSelectModule, SbdResultPanelComponent], 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: '720px', maxHeight: '90vh' }\"\n styleClass=\"sbd-action-dialog-wrapper\"\n header=\"Browse Actions\">\n\n <div class=\"sad\">\n\n <!-- ng-select dropdown -->\n <div class=\"sad__picker\">\n <ng-select\n [items]=\"actions()\"\n [ngModel]=\"selectedAction()\"\n (change)=\"onActionSelect($event)\"\n [searchable]=\"true\"\n [clearable]=\"false\"\n bindLabel=\"label\"\n placeholder=\"Search actions...\"\n class=\"sad__select\">\n\n <!-- Selected value display -->\n <ng-template ng-label-tmp let-item=\"item\">\n <div class=\"sad__option\">\n <i class=\"{{ item.icon }} sad__option-icon\"></i>\n <div class=\"sad__option-text\">\n <span class=\"sad__option-label\">{{ item.label }}</span>\n <span class=\"sad__option-sub\">{{ item.subtitle }}</span>\n </div>\n </div>\n </ng-template>\n\n <!-- Dropdown list item -->\n <ng-template ng-option-tmp let-item=\"item\" let-search=\"searchTerm\">\n <div class=\"sad__option\">\n <i class=\"{{ item.icon }} sad__option-icon\"></i>\n <div class=\"sad__option-text\">\n <span class=\"sad__option-label\">{{ item.label }}</span>\n <span class=\"sad__option-sub\">{{ item.subtitle }}</span>\n </div>\n </div>\n </ng-template>\n\n </ng-select>\n </div>\n\n <!-- Result area -->\n <div class=\"sad__result\">\n <app-sbd-result-panel\n [actionLabel]=\"selectedAction()?.label ?? ''\"\n [elapsedMs]=\"elapsedMs()\"\n [status]=\"actionStatus()\"\n [result]=\"resultContent()\"\n />\n </div>\n\n </div>\n\n <ng-template pTemplate=\"footer\">\n <button pButton label=\"Copy EN\" icon=\"pi pi-copy\"\n class=\"p-button-outlined p-button-sm\"\n [disabled]=\"actionStatus() !== 'success' || copying()\"\n (click)=\"copyResult('en')\">\n </button>\n <button pButton label=\"Copy AR\" icon=\"pi pi-copy\"\n class=\"p-button-outlined p-button-sm\"\n [disabled]=\"actionStatus() !== 'success' || copying()\"\n (click)=\"copyResult('ar')\">\n </button>\n <button pButton label=\"Copy EN + AR\"\n [icon]=\"copying() ? 'pi pi-spin pi-spinner' : 'pi pi-copy'\"\n class=\"p-button-outlined p-button-sm\"\n [disabled]=\"actionStatus() !== 'success' || copying()\"\n (click)=\"copyResult('both')\">\n </button>\n <button pButton label=\"Close\" icon=\"pi pi-times\" class=\"p-button-outlined p-button-secondary p-button-sm\" (click)=\"closed.emit()\"></button>\n </ng-template>\n\n</p-dialog>\n", styles: [".sad{display:flex;flex-direction:column;font-family:inherit}.sad__picker{padding:16px 20px;border-bottom:1px solid #e2e8f0}.sad__select{width:100%}.sad__select ::ng-deep .ng-select-container{height:56px}.sad__option{display:flex;align-items:center;gap:10px;padding:2px 0}.sad__option-icon{font-size:14px;color:#64748b;flex-shrink:0;width:20px;text-align:center}.sad__option-text{display:flex;flex-direction:column;gap:2px;min-width:0}.sad__option-label{font-size:13.5px;font-weight:600;color:#1e2e4a}.sad__option-sub{font-size:11.5px;color:#94a3b8}.sad__result{overflow-y:auto;max-height:calc(90vh - 200px)}.sad__result::-webkit-scrollbar{width:4px}.sad__result::-webkit-scrollbar-track{background:transparent}.sad__result::-webkit-scrollbar-thumb{background:#e2e8f0;border-radius:4px}.sad__result ::ng-deep .sbd-result-panel{border:none;border-radius:0;box-shadow:none}.sad__select.ng-select .ng-select-container{border:1px solid #e2e8f0;border-radius:8px;min-height:48px;font-family:inherit;box-shadow:none}.sad__select.ng-select .ng-select-container:hover{border-color:#94a3b8}.sad__select.ng-select .ng-select-container .ng-value-container{padding:0 12px}.sad__select.ng-select .ng-select-container .ng-value-container .ng-placeholder{color:#94a3b8;font-size:13.5px}.sad__select.ng-select .ng-select-container .ng-value-container .ng-value{font-size:13.5px}.sad__select.ng-select .ng-select-container .ng-arrow-wrapper{padding-right:10px}.sad__select.ng-select .ng-select-container .ng-arrow-wrapper .ng-arrow{border-color:#94a3b8 transparent transparent}.sad__select.ng-select.ng-select-focused .ng-select-container{border-color:#94a3b8;box-shadow:0 0 0 3px #94a3b826}.sad__select.ng-select.ng-select-opened .ng-select-container{border-color:#94a3b8;border-radius:8px 8px 0 0}.sad__select.ng-select.ng-select-opened .ng-select-container .ng-arrow{border-color:transparent transparent #94a3b8}.sad__select .ng-dropdown-panel{border:1px solid #e2e8f0;border-top:none;border-radius:0 0 8px 8px;box-shadow:0 8px 24px #00000014}.sad__select .ng-dropdown-panel .ng-dropdown-panel-items{max-height:520px}.sad__select .ng-dropdown-panel .ng-dropdown-panel-items .ng-option{padding:8px 12px;color:#1e2e4a;font-size:13.5px;background:#fff}.sad__select .ng-dropdown-panel .ng-dropdown-panel-items .ng-option:hover,.sad__select .ng-dropdown-panel .ng-dropdown-panel-items .ng-option.ng-option-marked{background:#f8fafc}.sad__select .ng-dropdown-panel .ng-dropdown-panel-items .ng-option.ng-option-selected{background:#f1f5f9;font-weight:500}.sad__select .ng-dropdown-panel .ng-dropdown-panel-items .ng-option.ng-option-selected .sad__option-label{color:#1e2e4a}.sad__select .ng-dropdown-panel .ng-dropdown-panel-items .ng-option.ng-option-selected .sad__option-icon{color:#475569}.sad__select .ng-dropdown-panel .ng-dropdown-header{border-bottom:1px solid #f1f5f9;padding:8px 12px}\n"] }]
3045
+ }], ctorParameters: () => [] });
3046
+
3047
+ class SbdAddNoteDialogComponent {
3048
+ constructor() {
3049
+ this.bookingService = inject(SbdBookingService);
3050
+ this.toast = inject(SbdToastService);
3051
+ this.visible = input(false);
3052
+ this.bookingId = input('');
3053
+ this.product = input('flight');
3054
+ this.closed = output();
3055
+ this.message = signal('');
3056
+ this.action = signal('GENERAL');
3057
+ this.submitting = signal(false);
3058
+ this.actionOptions = [
3059
+ { label: 'General', value: 'GENERAL', icon: 'pi pi-comment' },
3060
+ { label: 'Feedback', value: 'FEEDBACK', icon: 'pi pi-thumbs-up' },
3061
+ { label: 'Remark', value: 'REMARK', icon: 'pi pi-exclamation-circle' },
3062
+ ];
3063
+ effect(() => {
3064
+ if (!this.visible())
3065
+ this.reset();
3066
+ }, { allowSignalWrites: true });
3067
+ }
3068
+ submit() {
3069
+ const msg = this.message().trim();
3070
+ if (!msg)
3071
+ return;
3072
+ this.submitting.set(true);
3073
+ this.bookingService.addNote(this.bookingId(), this.product(), msg, this.action()).subscribe({
3074
+ next: () => {
3075
+ this.toast.success('Note added successfully');
3076
+ this.closed.emit();
3077
+ },
3078
+ error: () => {
3079
+ this.toast.error('Failed to add note. Please try again.');
3080
+ this.submitting.set(false);
3081
+ },
3082
+ });
3083
+ }
3084
+ reset() {
3085
+ this.message.set('');
3086
+ this.action.set('GENERAL');
3087
+ this.submitting.set(false);
3088
+ }
3089
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdAddNoteDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3090
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SbdAddNoteDialogComponent, isStandalone: true, selector: "app-sbd-add-note-dialog", inputs: { visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, bookingId: { classPropertyName: "bookingId", publicName: "bookingId", isSignal: true, isRequired: false, transformFunction: null }, product: { classPropertyName: "product", publicName: "product", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, ngImport: i0, 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\"\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:#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:#3b82f6;box-shadow:0 0 0 3px #3b82f61a}.and__textarea:disabled{background:#f8fafc;color:#94a3b8;cursor:not-allowed}\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: "directive", type: i2$1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i3$1.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "label", "icon", "loading"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: NgSelectModule }] }); }
3091
+ }
3092
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SbdAddNoteDialogComponent, decorators: [{
3093
+ type: Component,
3094
+ 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\"\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:#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:#3b82f6;box-shadow:0 0 0 3px #3b82f61a}.and__textarea:disabled{background:#f8fafc;color:#94a3b8;cursor:not-allowed}\n"] }]
3095
+ }], ctorParameters: () => [] });
3096
+
3097
+ class SmartBookingDashboardComponent {
3098
+ constructor() {
3099
+ this.bookingService = inject(SbdBookingService);
3100
+ this.toast = inject(SbdToastService);
3101
+ this.product = signal('flight');
3102
+ this.bookingId = signal('');
3103
+ this.bookingStatus = signal('idle');
3104
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3105
+ this.bookingData = signal(null);
3106
+ this.cancelDialogOpen = signal(false);
3107
+ this.actionDialogOpen = signal(false);
3108
+ this.addNoteDialogOpen = signal(false);
3109
+ this.selectedActionKey = signal(null);
3110
+ this.activeContextTab = signal('changes');
3111
+ this.availableActions = computed(() => (this.product() === 'flight' ? FLIGHT_ACTIONS : HOTEL_ACTIONS).filter(a => !a.hidden));
3112
+ // Handle both correct spelling (dataVerified) and BE typo (dataVerfied)
3113
+ this.dataVerified = computed(() => {
3114
+ const bi = this.bookingData()?.data?.booking_info;
3115
+ return bi?.dataVerified === true || bi?.dataVerfied === true;
3116
+ });
3117
+ // PNR list for the active booking, normalized to a flat array regardless of BE shape.
3118
+ this.cancelPnrInfo = computed(() => {
3119
+ const raw = this.bookingData()?.data?.booking_info?.pnr_info;
3120
+ if (Array.isArray(raw))
3121
+ return raw;
3122
+ if (raw?.pnr)
3123
+ return [{ pnr: raw.pnr, LegDirectionality: 'Departure' }];
3124
+ return [];
3125
+ });
3126
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3127
+ this.bookingHistory = computed(() => (this.bookingData()?.data?.booking_info?.history ?? []));
3128
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3129
+ this.bookingNotes = computed(() => (this.bookingData()?.data?.booking_info?.notes ?? []));
3130
+ this.historyGroups = computed(() => {
3131
+ const groups = new Map();
3132
+ for (const e of this.bookingHistory()) {
3133
+ if (!groups.has(e.changedAt))
3134
+ groups.set(e.changedAt, { changedBy: e.changedBy, changedAt: e.changedAt, entries: [] });
3135
+ const rawField = e.field.split('.').pop() ?? e.field;
3136
+ groups.get(e.changedAt).entries.push({
3137
+ field: rawField.replace(/\[.*?\]/g, ''),
3138
+ from: e.previousValue,
3139
+ to: e.newValue,
3140
+ });
3141
+ }
3142
+ return [...groups.values()];
3143
+ });
3144
+ }
3145
+ fmtNoteDate(date) {
3146
+ if (!date?.sec)
3147
+ return '—';
3148
+ try {
3149
+ return new Date(date.sec * 1000).toLocaleString('en-GB', {
3150
+ day: '2-digit', month: 'short', year: 'numeric',
3151
+ hour: '2-digit', minute: '2-digit',
3152
+ });
3153
+ }
3154
+ catch {
3155
+ return '—';
3156
+ }
3157
+ }
3158
+ formatField(field) {
3159
+ return field
3160
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
3161
+ .replace(/([a-z\d])([A-Z])/g, '$1 $2')
3162
+ .trim();
3163
+ }
3164
+ fmtDate(dt) {
3165
+ if (!dt)
3166
+ return '—';
3167
+ try {
3168
+ return new Date(dt).toLocaleString('en-GB', {
3169
+ day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit',
3170
+ });
3171
+ }
3172
+ catch {
3173
+ return dt;
3174
+ }
3175
+ }
3176
+ openAction(key) {
3177
+ this.selectedActionKey.set(key);
3178
+ this.actionDialogOpen.set(true);
3179
+ }
3180
+ onKeydown(event) {
3181
+ if ((event.metaKey || event.ctrlKey) && event.key === 'k') {
3182
+ event.preventDefault();
3183
+ document.querySelector('.sbd-booking-input__field')?.focus();
3184
+ }
3185
+ }
3186
+ lookupBooking() {
3187
+ const id = this.bookingId().trim();
3188
+ if (!id)
3189
+ return;
3190
+ this.bookingStatus.set('loading');
3191
+ this.bookingService.getBooking(id, this.product(), 'booking_info').subscribe({
3192
+ next: ({ data }) => {
3193
+ this.bookingData.set(data);
3194
+ this.bookingStatus.set('success');
3195
+ },
3196
+ error: () => {
3197
+ this.bookingStatus.set('error');
3198
+ this.bookingData.set(null);
3199
+ this.toast.error('Booking not found. Check the ID and try again.');
3200
+ },
3201
+ });
3202
+ }
3203
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SmartBookingDashboardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3204
+ 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 <!-- 2-column layout -->\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</div>\n\n<app-sbd-cancel-dialog\n [visible]=\"cancelDialogOpen()\"\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<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:#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: 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: "pipe", type: LowerCasePipe, name: "lowercase" }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i3$1.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "label", "icon", "loading"] }] }); }
3205
+ }
3206
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SmartBookingDashboardComponent, decorators: [{
3207
+ type: Component,
3208
+ args: [{ selector: 'app-smart-booking-dashboard', standalone: true, imports: [
3209
+ SbdBookingInputComponent,
3210
+ SbdWorkflowActionsComponent,
3211
+ SbdActionGridComponent,
3212
+ SbdCancelDialogComponent,
3213
+ SbdFlightItineraryCardComponent,
3214
+ SbdDataSearchComponent,
3215
+ SbdActionDialogComponent,
3216
+ SbdAddNoteDialogComponent,
3217
+ LowerCasePipe,
3218
+ ButtonModule,
3219
+ ], 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 <!-- 2-column layout -->\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</div>\n\n<app-sbd-cancel-dialog\n [visible]=\"cancelDialogOpen()\"\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<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:#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"] }]
3220
+ }], propDecorators: { onKeydown: [{
3221
+ type: HostListener,
3222
+ args: ['document:keydown', ['$event']]
3223
+ }] } });
3224
+
3225
+ var smartBookingDashboard_component = /*#__PURE__*/Object.freeze({
3226
+ __proto__: null,
3227
+ SmartBookingDashboardComponent: SmartBookingDashboardComponent
3228
+ });
3229
+
3230
+ const smartBookingDashboardRoutes = [
3231
+ {
3232
+ path: '',
3233
+ loadComponent: () => Promise.resolve().then(function () { return smartBookingDashboard_component; }).then(c => c.SmartBookingDashboardComponent),
3234
+ },
3235
+ ];
3236
+
3237
+ class SmartBookingDashboardModule {
3238
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SmartBookingDashboardModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
3239
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: SmartBookingDashboardModule, imports: [i1$3.RouterModule] }); }
3240
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SmartBookingDashboardModule, imports: [RouterModule.forChild(smartBookingDashboardRoutes)] }); }
3241
+ }
3242
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SmartBookingDashboardModule, decorators: [{
3243
+ type: NgModule,
3244
+ args: [{
3245
+ imports: [RouterModule.forChild(smartBookingDashboardRoutes)],
3246
+ }]
3247
+ }] });
3248
+
3249
+ /*
3250
+ * Public API of the Smart Booking Dashboard.
3251
+ * Host apps should import only from this file.
3252
+ */
3253
+
3254
+ /**
3255
+ * Generated bundle index. Do not edit.
3256
+ */
3257
+
3258
+ export { SBD_CONFIG, SBD_DEFAULT_CONFIG, SmartBookingDashboardComponent, SmartBookingDashboardModule, provideSmartBookingDashboard, smartBookingDashboardRoutes };
3259
+ //# sourceMappingURL=almatar-smart-booking-dashboard.mjs.map