tin-spa 20.5.1 → 20.5.6

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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, Inject, Component, InjectionToken, Optional, inject, Input, Directive, NgModule, EventEmitter, Output, forwardRef, HostListener, ViewChild, Pipe, ContentChild } from '@angular/core';
2
+ import { Injectable, Inject, Component, InjectionToken, Optional, inject, Input, Directive, NgModule, EventEmitter, Output, forwardRef, HostListener, ViewChild, Pipe, ContentChild, ViewEncapsulation } from '@angular/core';
3
3
  import * as i1 from '@angular/material/dialog';
4
4
  import { MAT_DIALOG_DATA, MatDialogModule, MAT_DIALOG_DEFAULT_OPTIONS, MatDialog } from '@angular/material/dialog';
5
5
  import * as i2 from '@angular/common';
@@ -10,7 +10,7 @@ import * as i4$1 from '@angular/material/icon';
10
10
  import { MatIconModule } from '@angular/material/icon';
11
11
  import * as i3 from '@angular/material/form-field';
12
12
  import { MatFormFieldModule, MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
13
- import { of, BehaviorSubject, tap, timeout, finalize, share, Observable, Subject, throwError, interval } from 'rxjs';
13
+ import { of, BehaviorSubject, Subject, tap, timeout, finalize, share, Observable, throwError, interval } from 'rxjs';
14
14
  import { mergeMap, filter, startWith, map, finalize as finalize$1, catchError, take, switchMap } from 'rxjs/operators';
15
15
  import * as i2$1 from '@angular/material/snack-bar';
16
16
  import { MatSnackBarModule } from '@angular/material/snack-bar';
@@ -42,7 +42,7 @@ import * as i14 from '@angular/material/list';
42
42
  import { MatListModule } from '@angular/material/list';
43
43
  import * as i4$4 from '@angular/material/menu';
44
44
  import { MatMenuModule } from '@angular/material/menu';
45
- import * as i12$2 from '@angular/material/paginator';
45
+ import * as i13 from '@angular/material/paginator';
46
46
  import { MatPaginatorModule } from '@angular/material/paginator';
47
47
  import * as i18$1 from '@angular/material/progress-spinner';
48
48
  import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
@@ -53,7 +53,7 @@ import { MatSliderModule } from '@angular/material/slider';
53
53
  import { MatSortModule } from '@angular/material/sort';
54
54
  import * as i4$3 from '@angular/material/stepper';
55
55
  import { MatStepperModule } from '@angular/material/stepper';
56
- import * as i11 from '@angular/material/table';
56
+ import * as i12$2 from '@angular/material/table';
57
57
  import { MatTableModule, MatTableDataSource } from '@angular/material/table';
58
58
  import * as i4$5 from '@angular/material/tabs';
59
59
  import { MatTabsModule } from '@angular/material/tabs';
@@ -1464,16 +1464,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
1464
1464
  }]
1465
1465
  }], ctorParameters: () => [] });
1466
1466
 
1467
- // SignalR service for real-time notification count push from backend hub
1467
+ // SignalR service for real-time notification count push and entity change broadcasts
1468
1468
  class SignalRService {
1469
1469
  constructor() {
1470
1470
  this.hubConnection = null;
1471
1471
  this.notificationCount = new BehaviorSubject(0);
1472
1472
  this.notificationCount$ = this.notificationCount.asObservable();
1473
- // Changed: Callback for reconnection count refresh (set by NotificationsService)
1473
+ // Changed: Data hub connection for real-time entity changes
1474
+ this.dataHubConnection = null;
1475
+ this.entityCreated = new Subject();
1476
+ this.entityUpdated = new Subject();
1477
+ this.entityDeleted = new Subject();
1478
+ this.entityCreated$ = this.entityCreated.asObservable(); // Changed: Observable for new entity rows
1479
+ this.entityUpdated$ = this.entityUpdated.asObservable(); // Changed: Observable for updated entity rows
1480
+ this.entityDeleted$ = this.entityDeleted.asObservable(); // Changed: Observable for deleted entity IDs
1481
+ // Changed: Callback for reconnection — notification count refresh (set by NotificationsService)
1474
1482
  this.onReconnected = null;
1483
+ // Changed: Callback for data hub reconnection — tables should reload (set by table components)
1484
+ this.onDataReconnected = null;
1475
1485
  }
1476
1486
  startConnection(hubUrl, token) {
1487
+ // Changed: Skip if already connected, connecting, or reconnecting to avoid aborting in-progress negotiation
1488
+ if (this.hubConnection && this.hubConnection.state !== signalR.HubConnectionState.Disconnected)
1489
+ return;
1477
1490
  if (this.hubConnection)
1478
1491
  this.stopConnection();
1479
1492
  this.hubConnection = new signalR.HubConnectionBuilder()
@@ -1489,7 +1502,35 @@ class SignalRService {
1489
1502
  if (this.onReconnected)
1490
1503
  this.onReconnected();
1491
1504
  });
1492
- this.hubConnection.start().catch(err => console.error('SignalR connection error:', err));
1505
+ this.hubConnection.start().catch(err => console.error('SignalR notification connection error:', err));
1506
+ }
1507
+ // Changed: Start data hub connection for real-time entity change broadcasts
1508
+ startDataConnection(hubUrl, token) {
1509
+ // Changed: Skip if already connected, connecting, or reconnecting to avoid aborting in-progress negotiation
1510
+ if (this.dataHubConnection && this.dataHubConnection.state !== signalR.HubConnectionState.Disconnected)
1511
+ return;
1512
+ if (this.dataHubConnection)
1513
+ this.stopDataConnection();
1514
+ this.dataHubConnection = new signalR.HubConnectionBuilder()
1515
+ .withUrl(hubUrl, { accessTokenFactory: () => token })
1516
+ .withAutomaticReconnect()
1517
+ .build();
1518
+ // Changed: Listen for entity CRUD broadcasts from backend DataHub
1519
+ this.dataHubConnection.on('EntityCreated', (entityName, data) => {
1520
+ this.entityCreated.next({ entityName, data });
1521
+ });
1522
+ this.dataHubConnection.on('EntityUpdated', (entityName, data) => {
1523
+ this.entityUpdated.next({ entityName, data });
1524
+ });
1525
+ this.dataHubConnection.on('EntityDeleted', (entityName, data) => {
1526
+ this.entityDeleted.next({ entityName, data });
1527
+ });
1528
+ // Changed: On data hub reconnect, notify tables to do a full reload (catch missed events)
1529
+ this.dataHubConnection.onreconnected(() => {
1530
+ if (this.onDataReconnected)
1531
+ this.onDataReconnected();
1532
+ });
1533
+ this.dataHubConnection.start().catch(err => console.error('SignalR data connection error:', err));
1493
1534
  }
1494
1535
  stopConnection() {
1495
1536
  if (this.hubConnection) {
@@ -1497,6 +1538,13 @@ class SignalRService {
1497
1538
  this.hubConnection = null;
1498
1539
  }
1499
1540
  }
1541
+ // Changed: Stop data hub connection
1542
+ stopDataConnection() {
1543
+ if (this.dataHubConnection) {
1544
+ this.dataHubConnection.stop();
1545
+ this.dataHubConnection = null;
1546
+ }
1547
+ }
1500
1548
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SignalRService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1501
1549
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SignalRService, providedIn: 'root' }); }
1502
1550
  }
@@ -1572,15 +1620,16 @@ class AuthService {
1572
1620
  // Changed: Enhanced clearSession with optional server-side revocation
1573
1621
  clearSession(revokeOnServer = false) {
1574
1622
  if (revokeOnServer) {
1575
- // Changed: Revoke refresh tokens on server before clearing local state
1576
- const token = this.tokenSource.value;
1577
- if (token) {
1578
- this.httpService.Post('User/revoke', {}).subscribe({
1623
+ // Changed: Revoke only this device's refresh token other devices keep their sessions
1624
+ const refreshToken = this.storage.getPersistent(Constants.AUTH_REFRESH_TOKEN);
1625
+ if (refreshToken) {
1626
+ this.httpService.Post('User/revoke', { refreshToken }).subscribe({
1579
1627
  error: () => { } // Silently ignore errors during logout
1580
1628
  });
1581
1629
  }
1582
1630
  }
1583
- this.signalRService.stopConnection(); // Changed: Stop SignalR connection before clearing session
1631
+ this.signalRService.stopConnection(); // Changed: Stop SignalR notification connection before clearing session
1632
+ this.signalRService.stopDataConnection(); // Changed: Stop SignalR data connection before clearing session
1584
1633
  this.Updateloggedin(false);
1585
1634
  this.UpdateAutoLogin(false);
1586
1635
  this.UpdateRole(new Role());
@@ -1718,6 +1767,15 @@ class AuthService {
1718
1767
  const refreshToken = this.storage.getPersistent(Constants.AUTH_REFRESH_TOKEN);
1719
1768
  return refreshToken !== null;
1720
1769
  }
1770
+ // Shared entry point for any page that needs to auto-redirect authenticated users
1771
+ // Returns true if session is active or was successfully restored via silent refresh
1772
+ async tryRestoreSession() {
1773
+ if (this.hasValidSession())
1774
+ return true;
1775
+ if (this.hasRefreshToken())
1776
+ return await this.silentRefresh();
1777
+ return false;
1778
+ }
1721
1779
  // Changed: Silently refresh the JWT using the stored refresh token
1722
1780
  silentRefresh() {
1723
1781
  if (this.isRefreshing)
@@ -1738,18 +1796,25 @@ class AuthService {
1738
1796
  resolve(true);
1739
1797
  }
1740
1798
  else {
1741
- // Refresh token invalidclear persistent data
1742
- this.storage.removePersistent(Constants.AUTH_REFRESH_TOKEN);
1743
- this.storage.removePersistent(Constants.AUTH_REFRESH_TOKEN_EXPIRE);
1744
- this.storage.removePersistent(Constants.AUTH_REMEMBER_ME);
1799
+ // Changed: Only clear token if it still matches another tab may have already rotated it
1800
+ const currentToken = this.storage.getPersistent(Constants.AUTH_REFRESH_TOKEN);
1801
+ if (currentToken === refreshToken) {
1802
+ this.storage.removePersistent(Constants.AUTH_REFRESH_TOKEN);
1803
+ this.storage.removePersistent(Constants.AUTH_REFRESH_TOKEN_EXPIRE);
1804
+ this.storage.removePersistent(Constants.AUTH_REMEMBER_ME);
1805
+ }
1745
1806
  resolve(false);
1746
1807
  }
1747
1808
  },
1748
1809
  error: () => {
1749
1810
  this.isRefreshing = false;
1750
- this.storage.removePersistent(Constants.AUTH_REFRESH_TOKEN);
1751
- this.storage.removePersistent(Constants.AUTH_REFRESH_TOKEN_EXPIRE);
1752
- this.storage.removePersistent(Constants.AUTH_REMEMBER_ME);
1811
+ // Changed: Only clear token if it still matches — another tab may have already rotated it
1812
+ const currentToken = this.storage.getPersistent(Constants.AUTH_REFRESH_TOKEN);
1813
+ if (currentToken === refreshToken) {
1814
+ this.storage.removePersistent(Constants.AUTH_REFRESH_TOKEN);
1815
+ this.storage.removePersistent(Constants.AUTH_REFRESH_TOKEN_EXPIRE);
1816
+ this.storage.removePersistent(Constants.AUTH_REMEMBER_ME);
1817
+ }
1753
1818
  resolve(false);
1754
1819
  }
1755
1820
  });
@@ -2170,7 +2235,9 @@ class DataServiceLib {
2170
2235
  { name: 'delete', dialog: true, action: { url: 'employees?action=delete', method: 'post' } },
2171
2236
  ],
2172
2237
  loadAction: { url: 'employees/all/x' },
2173
- formConfig: this.employeeFormConfig
2238
+ formConfig: this.employeeFormConfig,
2239
+ // realTime: true, // Disabled: testing realtime on transactions table only
2240
+ entityName: 'Employee' // Changed: Match backend entity name for SignalR broadcasts
2174
2241
  };
2175
2242
  //--------------------------Positions-------------------------
2176
2243
  this.positionsTableConfig = {
@@ -2205,7 +2272,9 @@ class DataServiceLib {
2205
2272
  { name: 'delete', dialog: true, action: { url: 'positions?action=delete', method: 'post' } },
2206
2273
  ],
2207
2274
  loadAction: { url: 'positions/all/x' },
2208
- formConfig: this.positionFormConfig
2275
+ formConfig: this.positionFormConfig,
2276
+ // realTime: true, // Disabled: testing realtime on transactions table only
2277
+ entityName: 'Position' // Changed: Match backend entity name for SignalR broadcasts
2209
2278
  };
2210
2279
  //--------------------------Departments-------------------------
2211
2280
  this.departmentTableConfig = {
@@ -2246,7 +2315,9 @@ class DataServiceLib {
2246
2315
  { name: 'delete', dialog: true, action: { url: 'departments?action=delete', method: 'post' } },
2247
2316
  ],
2248
2317
  loadAction: { url: 'departments/all/x' },
2249
- formConfig: this.departmentFormConfig
2318
+ formConfig: this.departmentFormConfig,
2319
+ // realTime: true, // Disabled: testing realtime on transactions table only
2320
+ entityName: 'Department' // Changed: Match backend entity name for SignalR broadcasts
2250
2321
  };
2251
2322
  //--------------------------Contact Persons-------------------------
2252
2323
  this.contactPersonFormConfig = {
@@ -2346,7 +2417,9 @@ class DataServiceLib {
2346
2417
  { name: 'delete', dialog: true, action: { url: 'customers?action=delete', method: 'post' } },
2347
2418
  ],
2348
2419
  loadAction: { url: 'customers/all/x' },
2349
- formConfig: this.customerFormConfig
2420
+ formConfig: this.customerFormConfig,
2421
+ // realTime: true, // Disabled: testing realtime on transactions table only — no re-fetch after CUD
2422
+ entityName: 'Customer' // Changed: Match backend typeof(Customer).Name for SignalR broadcasts
2350
2423
  };
2351
2424
  //--------------------------Suppliers-------------------------
2352
2425
  this.supplierFormConfig = {
@@ -2397,7 +2470,11 @@ class DataServiceLib {
2397
2470
  { name: 'delete', dialog: true, action: { url: 'suppliers?action=delete', method: 'post' } },
2398
2471
  ],
2399
2472
  loadAction: { url: 'suppliers/all/x' },
2400
- formConfig: this.supplierFormConfig
2473
+ formConfig: this.supplierFormConfig,
2474
+ allowUserKeepOpen: true, // Added: for testing — propagates to Create dialog (which has no detailsConfig of its own)
2475
+ keepOpenBehavior: 'reset', // Added: for testing — reset form after create so user can capture next supplier
2476
+ realTime: true, // Changed: Enabled for SignalR testing
2477
+ entityName: 'Supplier' // Changed: Match backend entity name for SignalR broadcasts
2401
2478
  };
2402
2479
  //--------------------------GPT Caches-------------------------
2403
2480
  this.gptCachesFormConfig = {
@@ -3681,7 +3758,9 @@ class AccountingService {
3681
3758
  ],
3682
3759
  loadAction: { url: 'invoices/all/x' },
3683
3760
  formConfig: this.invoiceFormConfig,
3684
- tileConfig: this.invoicesTileConfig
3761
+ tileConfig: this.invoicesTileConfig,
3762
+ // realTime: true, // Disabled: testing realtime on transactions table only
3763
+ entityName: 'Invoice'
3685
3764
  };
3686
3765
  //--------------------------Aging Report-------------------------
3687
3766
  // Added: Aging report summary tiles
@@ -3842,7 +3921,9 @@ class AccountingService {
3842
3921
  this.taxRateDeleteButton
3843
3922
  ],
3844
3923
  loadAction: { url: 'taxrates/all/x' },
3845
- formConfig: this.taxRateFormConfig
3924
+ formConfig: this.taxRateFormConfig,
3925
+ // realTime: true, // Disabled: testing realtime on transactions table only
3926
+ entityName: 'TaxRate'
3846
3927
  };
3847
3928
  //--------------------------Currencies-------------------------
3848
3929
  // Changed: Currency form configuration for multi-currency management
@@ -3887,7 +3968,9 @@ class AccountingService {
3887
3968
  this.currencyDeleteButton
3888
3969
  ],
3889
3970
  loadAction: { url: 'currencies/all/x' },
3890
- formConfig: this.currencyFormConfig
3971
+ formConfig: this.currencyFormConfig,
3972
+ // realTime: true, // Disabled: testing realtime on transactions table only
3973
+ entityName: 'Currency'
3891
3974
  };
3892
3975
  //--------------------------Transaction Types-------------------------
3893
3976
  this.transactionTypeFormConfig = {
@@ -3942,7 +4025,9 @@ class AccountingService {
3942
4025
  { name: 'delete', dialog: true, action: { url: 'transactiontypes?action=delete', method: 'post' } },
3943
4026
  ],
3944
4027
  loadAction: { url: 'transactiontypes/all/x' },
3945
- formConfig: this.transactionTypeFormConfig
4028
+ formConfig: this.transactionTypeFormConfig,
4029
+ // realTime: true, // Disabled: testing realtime on transactions table only
4030
+ entityName: 'TransactionType'
3946
4031
  };
3947
4032
  //--------------------------Transaction Import-------------------------
3948
4033
  // Edit form for a single import item (sub-dialog)
@@ -3958,6 +4043,7 @@ class AccountingService {
3958
4043
  };
3959
4044
  // Nested table showing imported items in the review dialog
3960
4045
  this.transactionImportItemsTableConfig = {
4046
+ tabTitle: 'Imported Items', // Changed: Add meaningful tab title instead of default 'Untitled'
3961
4047
  showFilter: false,
3962
4048
  causeFormRefresh: true,
3963
4049
  minColumns: ['date', 'description', 'amount', 'transactionTypeName'],
@@ -3983,7 +4069,9 @@ class AccountingService {
3983
4069
  ]
3984
4070
  },
3985
4071
  { name: 'errorMessage', type: 'text', alias: 'Error',
3986
- colors: [{ name: 'red', condition: (x) => !!x.errorMessage }] // Changed: Show error details in red
4072
+ colors: [
4073
+ { name: 'red', condition: (x) => !!x.errorMessage }
4074
+ ]
3987
4075
  }
3988
4076
  ],
3989
4077
  buttons: [
@@ -4046,7 +4134,7 @@ class AccountingService {
4046
4134
  title: 'Financial Transaction',
4047
4135
  fixedTitle: true,
4048
4136
  fields: [
4049
- { name: 'transactionTypeID', type: 'select', alias: 'Transaction Type', required: true, detailsConfig: this.transactionTypeDetailsConfig,
4137
+ { name: 'transactionTypeID', type: 'text-single', alias: 'Transaction Type', required: true, detailsConfig: this.transactionTypeDetailsConfig,
4050
4138
  loadAction: { url: 'transactionTypes/list/x' },
4051
4139
  },
4052
4140
  { name: 'date', type: 'date', required: true },
@@ -4109,7 +4197,25 @@ class AccountingService {
4109
4197
  this.transactionVoidButton,
4110
4198
  ],
4111
4199
  loadAction: { url: 'transactions/all/x' },
4112
- formConfig: this.transactionFormConfig
4200
+ formConfig: this.transactionFormConfig,
4201
+ realTime: true,
4202
+ entityName: 'Transaction',
4203
+ allowUserKeepOpen: true, // Added: shows Keep Open checkbox on transaction dialogs
4204
+ keepOpenBehavior: 'reset', // Added: reset form after create so user can capture next transaction
4205
+ };
4206
+ // Changed: Search mode config for transactions — date range, description contains, account filter
4207
+ this.transactionSearchTableConfig = {
4208
+ ...this.transactionsTableConfig,
4209
+ searchConfig: {
4210
+ fields: [
4211
+ { name: 'dateFrom', type: 'date', alias: 'From Date', show: true },
4212
+ { name: 'dateTo', type: 'date', alias: 'To Date', show: true },
4213
+ { name: 'description', type: 'text', alias: 'Description', show: true },
4214
+ { name: 'accountID', type: 'select', alias: 'Account', show: true, loadAction: { url: 'accounts/list/x' } },
4215
+ { name: 'typeName', type: 'text-single', alias: 'Transaction Type', show: true, loadAction: { url: 'transactiontypes/list/x' } }, // Changed: text-single for type-to-search autocomplete
4216
+ ],
4217
+ searchAction: { url: 'transactions/search', method: 'post' }
4218
+ }
4113
4219
  };
4114
4220
  this.accountTransactionsTableConfig = {
4115
4221
  ...this.transactionsTableConfig,
@@ -4291,7 +4397,9 @@ class AccountingService {
4291
4397
  this.standingOrderDeleteButton
4292
4398
  ],
4293
4399
  loadAction: { url: 'standingorders/all/x' },
4294
- formConfig: this.standingOrderFormConfig
4400
+ formConfig: this.standingOrderFormConfig,
4401
+ // realTime: true, // Disabled: testing realtime on transactions table only
4402
+ entityName: 'StandingOrder'
4295
4403
  };
4296
4404
  //--------------------------Accounts-------------------------
4297
4405
  this.accountDetailsConfig = {
@@ -4327,13 +4435,15 @@ class AccountingService {
4327
4435
  { name: 'view', dialog: true, detailsConfig: this.accountDetailsConfig },
4328
4436
  this.finAccounEditButton,
4329
4437
  { name: 'delete', dialog: true, action: { url: 'accounts?action=delete', method: 'post' } },
4330
- { name: 'revalue', display: 'Revalue Forex', inHeader: true, icon: { name: 'currency_exchange', color: 'blue' }, // Changed: Added forex revaluation button
4331
- action: { url: 'accounts?action=revalue', method: 'post', successMessage: 'Forex revaluation complete' },
4332
- confirm: { message: 'Revalue all forex accounts at current exchange rates?' }
4333
- },
4438
+ // { name: 'revalue', display: 'Revalue Forex', inHeader: true, icon: { name: 'currency_exchange', color: 'blue' }, // Changed: Commented out — forex revaluation deferred
4439
+ // action: { url: 'accounts?action=revalue', method: 'post', successMessage: 'Forex revaluation complete' },
4440
+ // confirm: { message: 'Revalue all forex accounts at current exchange rates?' }
4441
+ // },
4334
4442
  ],
4335
4443
  loadAction: { url: 'accounts/all/x' },
4336
- formConfig: this.accountFormConfig
4444
+ formConfig: this.accountFormConfig,
4445
+ // realTime: true, // Disabled: testing realtime on transactions table only
4446
+ entityName: 'Account'
4337
4447
  };
4338
4448
  //--------------------------Financial Reports-------------------------
4339
4449
  // Trial Balance table config — shows all accounts with debit/credit totals
@@ -4493,10 +4603,12 @@ class AccountingService {
4493
4603
  { name: 'updatedByDetails', alias: 'Last Updated', type: 'text' },
4494
4604
  ],
4495
4605
  buttons: [
4496
- { name: 'create', dialog: true, detailsConfig: this.categoryDetailsConfig, action: { url: 'fixedassetcategories?action=create', method: 'post' } },
4606
+ { name: 'create', display: 'Create Category', dialog: true, detailsConfig: this.categoryDetailsConfig, action: { url: 'fixedassetcategories?action=create', method: 'post' } }, // Changed: Added missing display text
4497
4607
  { name: 'delete', display: 'Delete', icon: { name: 'delete', color: 'red' }, action: { url: 'fixedassetcategories?action=delete', method: 'post' }, confirm: { message: 'Delete this category?' } },
4498
4608
  ],
4499
4609
  loadAction: { url: 'fixedassetcategories/all/x' },
4610
+ // realTime: true, // Disabled: testing realtime on transactions table only
4611
+ entityName: 'FixedAssetCategory'
4500
4612
  };
4501
4613
  //--------------------------Fixed Assets-------------------------
4502
4614
  // Fixed asset form config for create/edit dialog
@@ -4593,13 +4705,15 @@ class AccountingService {
4593
4705
  { name: 'updatedByDetails', alias: 'Last Updated', type: 'text' },
4594
4706
  ],
4595
4707
  buttons: [
4596
- { name: 'create', dialog: true, detailsConfig: this.assetDetailsConfig, action: { url: 'fixedassets?action=create', method: 'post' } },
4708
+ { name: 'create', display: 'Create Asset', dialog: true, detailsConfig: this.assetDetailsConfig, action: { url: 'fixedassets?action=create', method: 'post' } }, // Changed: Added missing display text
4597
4709
  this.assetActivateButton,
4598
4710
  this.assetDepreciateButton,
4599
4711
  { name: 'delete', display: 'Delete', icon: { name: 'delete', color: 'red' }, action: { url: 'fixedassets?action=delete', method: 'post' }, confirm: { message: 'Delete this asset?' }, visible: x => x.status == AssetStatus.Draft },
4600
4712
  ],
4601
4713
  loadAction: { url: 'fixedassets/all/x' },
4602
- tileConfig: this.assetTileConfig
4714
+ tileConfig: this.assetTileConfig,
4715
+ // realTime: true, // Disabled: testing realtime on transactions table only
4716
+ entityName: 'FixedAsset'
4603
4717
  };
4604
4718
  //--------------------------Budgets-------------------------
4605
4719
  // Budget form configuration
@@ -4693,7 +4807,9 @@ class AccountingService {
4693
4807
  { name: 'delete', inDialog: true, icon: { name: 'delete', color: 'red' }, action: { url: 'budgets?action=delete', method: 'post', successMessage: 'Budget deleted' }, confirm: { message: 'Delete this budget?' }, visible: (x) => x.status == 1 }
4694
4808
  ],
4695
4809
  loadAction: { url: 'budgets/all/x' },
4696
- formConfig: this.budgetFormConfig
4810
+ formConfig: this.budgetFormConfig,
4811
+ // realTime: true, // Disabled: testing realtime on transactions table only
4812
+ entityName: 'Budget'
4697
4813
  };
4698
4814
  //--------------------------Budget vs Actual Report-------------------------
4699
4815
  // Budget vs Actual - By Period detail table
@@ -4920,7 +5036,9 @@ class LoansService {
4920
5036
  loadAction: { url: 'loans/all/x' },
4921
5037
  minColumns: ['borrowerName', 'principalAmount', 'statusName'],
4922
5038
  formConfig: this.loanFormConfig,
4923
- buttons: [this.loanViewButton, this.loanCreateButton, this.loanEditButton, this.loanDisburseButton]
5039
+ buttons: [this.loanViewButton, this.loanCreateButton, this.loanEditButton, this.loanDisburseButton],
5040
+ // realTime: true, // Disabled: testing realtime on transactions table only
5041
+ entityName: 'Loan'
4924
5042
  };
4925
5043
  // ========== LOAN PAYMENTS BUTTONS ==========
4926
5044
  this.loanPaymentCreateButton = { name: 'create', display: 'Record Payment', dialog: true, action: { url: 'loanpayments?action=create', method: 'post' } };
@@ -4951,7 +5069,9 @@ class LoansService {
4951
5069
  loadAction: { url: 'loanpayments/all/x' },
4952
5070
  minColumns: ['paymentDate', 'customerName', 'amount'],
4953
5071
  formConfig: this.loanPaymentFormConfig,
4954
- buttons: [this.loanPaymentCreateButton]
5072
+ buttons: [this.loanPaymentCreateButton],
5073
+ // realTime: true, // Disabled: testing realtime on transactions table only
5074
+ entityName: 'LoanPayment'
4955
5075
  };
4956
5076
  }
4957
5077
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: LoansService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
@@ -5002,7 +5122,9 @@ class GeneralService {
5002
5122
  this.categoryDeleteButton
5003
5123
  ],
5004
5124
  loadAction: { url: 'categories/all/x' },
5005
- formConfig: this.categoryFormConfig
5125
+ formConfig: this.categoryFormConfig,
5126
+ // realTime: true, // Disabled: testing realtime on transactions table only
5127
+ entityName: 'Category'
5006
5128
  };
5007
5129
  //--------------------------SubCategories-------------------------
5008
5130
  this.subCategoryFormConfig = {
@@ -5041,7 +5163,9 @@ class GeneralService {
5041
5163
  this.subCategoryDeleteButton
5042
5164
  ],
5043
5165
  loadAction: { url: 'subcategories/all/x' },
5044
- formConfig: this.subCategoryFormConfig
5166
+ formConfig: this.subCategoryFormConfig,
5167
+ // realTime: true, // Disabled: testing realtime on transactions table only
5168
+ entityName: 'SubCategory'
5045
5169
  };
5046
5170
  //--------------------------Brands-------------------------
5047
5171
  this.brandFormConfig = {
@@ -5077,7 +5201,9 @@ class GeneralService {
5077
5201
  this.brandDeleteButton
5078
5202
  ],
5079
5203
  loadAction: { url: 'brands/all/x' },
5080
- formConfig: this.brandFormConfig
5204
+ formConfig: this.brandFormConfig,
5205
+ // realTime: true, // Disabled: testing realtime on transactions table only
5206
+ entityName: 'Brand'
5081
5207
  };
5082
5208
  //--------------------------ListItems-------------------------
5083
5209
  this.listItemFormConfig = {
@@ -5113,7 +5239,9 @@ class GeneralService {
5113
5239
  this.listItemDeleteButton
5114
5240
  ],
5115
5241
  loadAction: { url: 'listitems/all/x' },
5116
- formConfig: this.listItemFormConfig
5242
+ formConfig: this.listItemFormConfig,
5243
+ // realTime: true, // Disabled: testing realtime on transactions table only
5244
+ entityName: 'ListItem'
5117
5245
  };
5118
5246
  }
5119
5247
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: GeneralService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
@@ -5187,7 +5315,9 @@ class InventoryService {
5187
5315
  this.productDeleteButton,
5188
5316
  ],
5189
5317
  loadAction: { url: 'products/all/x' },
5190
- formConfig: this.productFormConfig
5318
+ formConfig: this.productFormConfig,
5319
+ // realTime: true, // Disabled: testing realtime on transactions table only
5320
+ entityName: 'Product'
5191
5321
  };
5192
5322
  //--------------------------Product Components (Bundle Configuration)-------------------------
5193
5323
  // Product component form for adding/editing bundle components
@@ -5329,7 +5459,9 @@ class InventoryService {
5329
5459
  this.serviceItemDeleteButton
5330
5460
  ],
5331
5461
  loadAction: { url: 'serviceitems/all/x' },
5332
- formConfig: this.serviceItemFormConfig
5462
+ formConfig: this.serviceItemFormConfig,
5463
+ // realTime: true, // Disabled: testing realtime on transactions table only
5464
+ entityName: 'ServiceItem'
5333
5465
  };
5334
5466
  //--------------------------Requisitions-------------------------
5335
5467
  this.requisitionItemFormConfig = {
@@ -5418,7 +5550,9 @@ class InventoryService {
5418
5550
  this.requisitionCancelButton
5419
5551
  ],
5420
5552
  loadAction: { url: 'requisitions/all/x' },
5421
- formConfig: this.requisitionFormConfig
5553
+ formConfig: this.requisitionFormConfig,
5554
+ // realTime: true, // Disabled: testing realtime on transactions table only
5555
+ entityName: 'Requisition'
5422
5556
  };
5423
5557
  // Removed: Purchase Orders configs moved to PurchasingService
5424
5558
  // Removed: Inventory Receipts configs moved to PurchasingService
@@ -5462,7 +5596,9 @@ class InventoryService {
5462
5596
  { name: 'view', dialog: true, detailsConfig: this.inventoryItemDetailsConfig }
5463
5597
  ],
5464
5598
  loadAction: { url: 'inventoryitems/all/x' },
5465
- formConfig: this.inventoryItemFormConfig
5599
+ formConfig: this.inventoryItemFormConfig,
5600
+ // realTime: true, // Disabled: testing realtime on transactions table only
5601
+ entityName: 'InventoryItem'
5466
5602
  };
5467
5603
  //--------------------------Inventory Adjustments-------------------------
5468
5604
  // Updated: Changed form from all-readonly to editable fields for create/edit
@@ -5515,7 +5651,9 @@ class InventoryService {
5515
5651
  this.inventoryAdjustmentDeleteButton
5516
5652
  ],
5517
5653
  loadAction: { url: 'inventoryadjustments/all/x' },
5518
- formConfig: this.inventoryAdjustmentFormConfig
5654
+ formConfig: this.inventoryAdjustmentFormConfig,
5655
+ // realTime: true, // Disabled: testing realtime on transactions table only
5656
+ entityName: 'InventoryAdjustment'
5519
5657
  };
5520
5658
  //--------------------------Inventory Returns-------------------------
5521
5659
  // Added: Return item form config with conditional fields based on return type
@@ -5619,7 +5757,9 @@ class InventoryService {
5619
5757
  this.inventoryReturnRejectButton
5620
5758
  ],
5621
5759
  loadAction: { url: 'inventoryreturns/all/x' },
5622
- formConfig: this.inventoryReturnFormConfig
5760
+ formConfig: this.inventoryReturnFormConfig,
5761
+ // realTime: true, // Disabled: testing realtime on transactions table only
5762
+ entityName: 'InventoryReturn'
5623
5763
  };
5624
5764
  //--------------------------Inventory Transactions-------------------------
5625
5765
  this.inventoryTransactionFormConfig = {
@@ -5660,7 +5800,9 @@ class InventoryService {
5660
5800
  { name: 'view', dialog: true, detailsConfig: this.inventoryTransactionDetailsConfig }
5661
5801
  ],
5662
5802
  loadAction: { url: 'inventorytransactions/all/x' },
5663
- formConfig: this.inventoryTransactionFormConfig
5803
+ formConfig: this.inventoryTransactionFormConfig,
5804
+ // realTime: true, // Disabled: testing realtime on transactions table only
5805
+ entityName: 'InventoryTransaction'
5664
5806
  };
5665
5807
  //--------------------------Inventory Stock (Grouped by Product)-------------------------
5666
5808
  // Changed: Reuse existing inventoryItemsTableConfig with product criteria filter
@@ -6259,9 +6401,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
6259
6401
 
6260
6402
  // Functional guard for Angular 15+ (replaces deprecated class-based CanActivate)
6261
6403
  // Changed: Returns Promise<boolean> to properly await token refresh before allowing navigation
6262
- const authGuard = (route, state) => {
6404
+ const authGuard = async (route, state) => {
6263
6405
  const authService = inject(AuthService);
6264
- return authService.checkAuthentication();
6406
+ const signalRService = inject(SignalRService);
6407
+ const httpService = inject(HttpService);
6408
+ const storageService = inject(StorageService);
6409
+ const isAuthenticated = await authService.checkAuthentication();
6410
+ // Changed: Ensure SignalR data hub connection is started on every authenticated route access
6411
+ if (isAuthenticated) {
6412
+ const dataHubUrl = httpService.apiUrl.replace(/\/api\/$/, '/hubs/data');
6413
+ const token = await storageService.get(Constants.AUTH_TOKEN);
6414
+ if (token)
6415
+ signalRService.startDataConnection(dataHubUrl, token);
6416
+ }
6417
+ return isAuthenticated;
6265
6418
  };
6266
6419
 
6267
6420
  // Functional guard for plan-based feature gating — blocks navigation to restricted routes
@@ -10633,6 +10786,7 @@ class DetailsDialogLite {
10633
10786
  this.authService = authService;
10634
10787
  this.tableConfigService = tableConfigService;
10635
10788
  this.autoRefreshEnabled = false;
10789
+ this.userKeepOpen = false; // Added: runtime state for the user-controlled Keep Open checkbox
10636
10790
  this.titleAction = "View";
10637
10791
  this.loadByAction = false;
10638
10792
  this.files = [];
@@ -10856,9 +11010,15 @@ class DetailsDialogLite {
10856
11010
  }
10857
11011
  this.processButtonAction(button);
10858
11012
  }
11013
+ // Added: returns the effective keepOpen value — user checkbox takes precedence when allowUserKeepOpen is enabled
11014
+ effectiveKeepOpen(button) {
11015
+ if (this.detailsConfig.allowUserKeepOpen)
11016
+ return this.userKeepOpen;
11017
+ return button.keepOpen ?? false;
11018
+ }
10859
11019
  processButtonAction(button) {
10860
11020
  if (!button.action) {
10861
- if (!button.keepOpen) {
11021
+ if (!this.effectiveKeepOpen(button)) { // Changed: use effectiveKeepOpen to respect user checkbox
10862
11022
  this.dialogRef.close({ message: 'emit', data: this.details });
10863
11023
  }
10864
11024
  return;
@@ -10938,8 +11098,12 @@ class DetailsDialogLite {
10938
11098
  if (button.onSuccess) {
10939
11099
  button.onSuccess(apiResponse.data, this.details);
10940
11100
  }
10941
- if (button.keepOpen) {
10942
- if (button.name === 'create' && apiResponse.data) {
11101
+ if (this.effectiveKeepOpen(button)) { // Changed: use effectiveKeepOpen to respect user checkbox
11102
+ if (button.name === 'create' && this.detailsConfig.allowUserKeepOpen && this.userKeepOpen && this.detailsConfig.keepOpenBehavior === 'reset') {
11103
+ // Changed: User checkbox keep-open on create with 'reset' behavior — clear form for next capture, stay in create mode
11104
+ this.details = Core.generateObject(this.formConfig.fields);
11105
+ }
11106
+ else if (button.name === 'create' && apiResponse.data) {
10943
11107
  this.details = apiResponse.data;
10944
11108
  if (this.detailsConfig.heroField && apiResponse.data[this.detailsConfig.heroField]) {
10945
11109
  this.detailsConfig.heroValue = apiResponse.data[this.detailsConfig.heroField];
@@ -10984,11 +11148,11 @@ class DetailsDialogLite {
10984
11148
  }
10985
11149
  }
10986
11150
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DetailsDialogLite, deps: [{ token: i1$3.BreakpointObserver }, { token: LoaderService }, { token: DataServiceLib }, { token: MessageService }, { token: i1.MatDialogRef }, { token: MAT_DIALOG_DATA }, { token: ButtonService }, { token: DialogService }, { token: AuthService }, { token: TableConfigService }], target: i0.ɵɵFactoryTarget.Component }); }
10987
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: DetailsDialogLite, isStandalone: false, selector: "spa-detailsDialog-lite", outputs: { inputChange: "inputChange" }, ngImport: i0, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <spa-check *ngIf=\"detailsConfig.autoRefreshConfig\" display=\"Auto Refresh\" [(value)]=\"autoRefreshEnabled\" (valueChange)=\"toggleAutoRefresh()\"></spa-check>\r\n\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && testVisible(details,editButton.name)\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"testDisabled(details,editButton.name)\"><mat-icon>edit</mat-icon></button>\r\n </div>\r\n\r\n <button [disabled]=\"isProcessing\" *ngIf=\"loadByAction\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Refresh\" color=\"primary\" (click)=\"loadData(formConfig.loadAction, detailsConfig.causeTableRefresh)\"><mat-icon class=\"refreshIcon\">cached</mat-icon></button>\r\n \r\n <!-- Added: Top close button when position is 'top' -->\r\n <button *ngIf=\"shouldShowTopClose()\" [disabled]=\"isProcessing\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Close\" (click)=\"close()\"><mat-icon>close</mat-icon></button>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div style=\"padding-left: 24px; padding-right: 24px;\">\r\n <spa-steps *ngIf=\"stepConfig && details && stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n </div>\r\n\r\n <mat-dialog-content class=\"mat-typography dialog-scroll-content\">\r\n\r\n <spa-steps *ngIf=\"stepConfig && details && !stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && !statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && !formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n\r\n <div class=\"tin-input\" style=\"font-size:14px\">\r\n\r\n <p *ngIf=\"formConfig && !details\"><em>Loading...</em></p>\r\n\r\n <spa-form *ngIf=\"formConfig && details\" [files]=\"files\" [data]=\"details\" [config]=\"formConfig\" (inputChange)=\"inputChanged($event)\">\r\n <ng-template #dynamicSelect let-field=\"field\" let-data=\"data\" let-testReadOnly=\"testReadOnly\" let-testRequired=\"testRequired\" let-selectChanged=\"selectChanged\">\r\n <spa-select-lite\r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [width]=\"field.width\"\r\n [nullable]=\"field.nullable\"\r\n [options]=\"field.options\"\r\n [masterOptions]=\"field.masterOptions\"\r\n [masterField]=\"field.masterField\"\r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\"\r\n [optionValue]=\"field.optionValue ?? 'value'\"\r\n [(value)]=\"data[field.name]\"\r\n [defaultFirstValue]=\"field.defaultFirstValue\"\r\n [required]=\"testRequired(field)\"\r\n [readonly]=\"testReadOnly(field)\"\r\n [hint]=\"field.hint\"\r\n \r\n [loadAction]=\"field.loadAction\"\r\n [loadIDField]=\"field.loadIDField\"\r\n [field]=\"field\"\r\n [data]=\"data\"\r\n [infoMessage]=\"field.infoMessage\"\r\n [copyContent]=\"field.copyContent\"\r\n (valueChange)=\"selectChanged(field)\"\r\n ></spa-select-lite>\r\n </ng-template>\r\n </spa-form>\r\n\r\n <!-- \r\n Lite Dialog should not implement tabs \r\n PlaceHolder\r\n \r\n \r\n \r\n \r\n \r\n -->\r\n </div>\r\n\r\n </mat-dialog-content>\r\n\r\n\r\n </div>\r\n\r\n <mat-dialog-actions>\r\n\r\n <div>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='create' && createButton\" color=\"primary\" \r\n (click)=\"create()\" cdkFocusInitial>{{createButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='edit' && editButton\" color=\"primary\" \r\n (click)=\"edit()\" cdkFocusInitial>{{editButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <ng-container *ngFor=\"let btn of extraButtons\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && testVisible(details,btn.name)\" mat-stroked-button [disabled]=\"isProcessing || testDisabled(details,btn.name)\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\" (click)=\"custom(btn)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"btn.icon\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\">{{btn.icon.name}}</mat-icon>\r\n {{btn.display ?? btn.name | titlecase}}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Changed: Bottom close button now uses conditional display and custom text -->\r\n <button *ngIf=\"shouldShowBottomClose()\" mat-stroked-button color=\"primary\" (click)=\"close()\">{{getCloseText()}}</button>\r\n\r\n </div>\r\n\r\n <div class=\"col d-flex justify-content-end\" *ngIf=\"smallScreen\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Delete\" [disabled]=\"isProcessing\" style=\"color: red;\" (click)=\"delete()\" *ngIf=\"formConfig.mode!='create' && deleteButton\"><mat-icon>delete</mat-icon></button>\r\n </div>\r\n\r\n\r\n </mat-dialog-actions>\r\n\r\n\r\n</div>\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: i14$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: CheckComponent, selector: "spa-check", inputs: ["readonly", "display", "value", "infoMessage"], outputs: ["valueChange", "click", "check", "uncheck", "infoClick"] }, { kind: "component", type: StepsComponent, selector: "spa-steps", inputs: ["value", "config", "data"] }, { kind: "component", type: FormComponent, selector: "spa-form", inputs: ["files", "data", "config"], outputs: ["buttonClick", "inputChange"] }, { kind: "component", type: AlertComponent, selector: "spa-alert", inputs: ["config", "data"] }, { kind: "component", type: SelectLiteComponent, selector: "spa-select-lite" }, { kind: "component", type: StatusesComponent, selector: "spa-statuses", inputs: ["config", "data"] }, { kind: "pipe", type: i2.TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
11151
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: DetailsDialogLite, isStandalone: false, selector: "spa-detailsDialog-lite", outputs: { inputChange: "inputChange" }, ngImport: i0, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <spa-check *ngIf=\"detailsConfig.autoRefreshConfig\" display=\"Auto Refresh\" [(value)]=\"autoRefreshEnabled\" (valueChange)=\"toggleAutoRefresh()\"></spa-check>\r\n <!-- Added: Keep Open checkbox \u2014 visible when developer enables allowUserKeepOpen -->\r\n <spa-check *ngIf=\"detailsConfig.allowUserKeepOpen\" display=\"Keep Open\" [(value)]=\"userKeepOpen\"></spa-check>\r\n\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && testVisible(details,editButton.name)\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"testDisabled(details,editButton.name)\"><mat-icon>edit</mat-icon></button>\r\n </div>\r\n\r\n <button [disabled]=\"isProcessing\" *ngIf=\"loadByAction\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Refresh\" color=\"primary\" (click)=\"loadData(formConfig.loadAction, detailsConfig.causeTableRefresh)\"><mat-icon class=\"refreshIcon\">cached</mat-icon></button>\r\n \r\n <!-- Added: Top close button when position is 'top' -->\r\n <button *ngIf=\"shouldShowTopClose()\" [disabled]=\"isProcessing\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Close\" (click)=\"close()\"><mat-icon>close</mat-icon></button>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div style=\"padding-left: 24px; padding-right: 24px;\">\r\n <spa-steps *ngIf=\"stepConfig && details && stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n </div>\r\n\r\n <mat-dialog-content class=\"mat-typography dialog-scroll-content\">\r\n\r\n <spa-steps *ngIf=\"stepConfig && details && !stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && !statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && !formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n\r\n <div class=\"tin-input\" style=\"font-size:14px\">\r\n\r\n <p *ngIf=\"formConfig && !details\"><em>Loading...</em></p>\r\n\r\n <spa-form *ngIf=\"formConfig && details\" [files]=\"files\" [data]=\"details\" [config]=\"formConfig\" (inputChange)=\"inputChanged($event)\">\r\n <ng-template #dynamicSelect let-field=\"field\" let-data=\"data\" let-testReadOnly=\"testReadOnly\" let-testRequired=\"testRequired\" let-selectChanged=\"selectChanged\">\r\n <spa-select-lite\r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [width]=\"field.width\"\r\n [nullable]=\"field.nullable\"\r\n [options]=\"field.options\"\r\n [masterOptions]=\"field.masterOptions\"\r\n [masterField]=\"field.masterField\"\r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\"\r\n [optionValue]=\"field.optionValue ?? 'value'\"\r\n [(value)]=\"data[field.name]\"\r\n [defaultFirstValue]=\"field.defaultFirstValue\"\r\n [required]=\"testRequired(field)\"\r\n [readonly]=\"testReadOnly(field)\"\r\n [hint]=\"field.hint\"\r\n \r\n [loadAction]=\"field.loadAction\"\r\n [loadIDField]=\"field.loadIDField\"\r\n [field]=\"field\"\r\n [data]=\"data\"\r\n [infoMessage]=\"field.infoMessage\"\r\n [copyContent]=\"field.copyContent\"\r\n (valueChange)=\"selectChanged(field)\"\r\n ></spa-select-lite>\r\n </ng-template>\r\n </spa-form>\r\n\r\n <!-- \r\n Lite Dialog should not implement tabs \r\n PlaceHolder\r\n \r\n \r\n \r\n \r\n \r\n -->\r\n </div>\r\n\r\n </mat-dialog-content>\r\n\r\n\r\n </div>\r\n\r\n <mat-dialog-actions>\r\n\r\n <div>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='create' && createButton\" color=\"primary\" \r\n (click)=\"create()\" cdkFocusInitial>{{createButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='edit' && editButton\" color=\"primary\" \r\n (click)=\"edit()\" cdkFocusInitial>{{editButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <ng-container *ngFor=\"let btn of extraButtons\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && testVisible(details,btn.name)\" mat-stroked-button [disabled]=\"isProcessing || testDisabled(details,btn.name)\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\" (click)=\"custom(btn)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"btn.icon\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\">{{btn.icon.name}}</mat-icon>\r\n {{btn.display ?? btn.name | titlecase}}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Changed: Bottom close button now uses conditional display and custom text -->\r\n <button *ngIf=\"shouldShowBottomClose()\" mat-stroked-button color=\"primary\" (click)=\"close()\">{{getCloseText()}}</button>\r\n\r\n </div>\r\n\r\n <div class=\"col d-flex justify-content-end\" *ngIf=\"smallScreen\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Delete\" [disabled]=\"isProcessing\" style=\"color: red;\" (click)=\"delete()\" *ngIf=\"formConfig.mode!='create' && deleteButton\"><mat-icon>delete</mat-icon></button>\r\n </div>\r\n\r\n\r\n </mat-dialog-actions>\r\n\r\n\r\n</div>\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: i14$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: CheckComponent, selector: "spa-check", inputs: ["readonly", "display", "value", "infoMessage"], outputs: ["valueChange", "click", "check", "uncheck", "infoClick"] }, { kind: "component", type: StepsComponent, selector: "spa-steps", inputs: ["value", "config", "data"] }, { kind: "component", type: FormComponent, selector: "spa-form", inputs: ["files", "data", "config"], outputs: ["buttonClick", "inputChange"] }, { kind: "component", type: AlertComponent, selector: "spa-alert", inputs: ["config", "data"] }, { kind: "component", type: SelectLiteComponent, selector: "spa-select-lite" }, { kind: "component", type: StatusesComponent, selector: "spa-statuses", inputs: ["config", "data"] }, { kind: "pipe", type: i2.TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
10988
11152
  }
10989
11153
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DetailsDialogLite, decorators: [{
10990
11154
  type: Component,
10991
- args: [{ selector: 'spa-detailsDialog-lite', standalone: false, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <spa-check *ngIf=\"detailsConfig.autoRefreshConfig\" display=\"Auto Refresh\" [(value)]=\"autoRefreshEnabled\" (valueChange)=\"toggleAutoRefresh()\"></spa-check>\r\n\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && testVisible(details,editButton.name)\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"testDisabled(details,editButton.name)\"><mat-icon>edit</mat-icon></button>\r\n </div>\r\n\r\n <button [disabled]=\"isProcessing\" *ngIf=\"loadByAction\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Refresh\" color=\"primary\" (click)=\"loadData(formConfig.loadAction, detailsConfig.causeTableRefresh)\"><mat-icon class=\"refreshIcon\">cached</mat-icon></button>\r\n \r\n <!-- Added: Top close button when position is 'top' -->\r\n <button *ngIf=\"shouldShowTopClose()\" [disabled]=\"isProcessing\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Close\" (click)=\"close()\"><mat-icon>close</mat-icon></button>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div style=\"padding-left: 24px; padding-right: 24px;\">\r\n <spa-steps *ngIf=\"stepConfig && details && stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n </div>\r\n\r\n <mat-dialog-content class=\"mat-typography dialog-scroll-content\">\r\n\r\n <spa-steps *ngIf=\"stepConfig && details && !stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && !statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && !formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n\r\n <div class=\"tin-input\" style=\"font-size:14px\">\r\n\r\n <p *ngIf=\"formConfig && !details\"><em>Loading...</em></p>\r\n\r\n <spa-form *ngIf=\"formConfig && details\" [files]=\"files\" [data]=\"details\" [config]=\"formConfig\" (inputChange)=\"inputChanged($event)\">\r\n <ng-template #dynamicSelect let-field=\"field\" let-data=\"data\" let-testReadOnly=\"testReadOnly\" let-testRequired=\"testRequired\" let-selectChanged=\"selectChanged\">\r\n <spa-select-lite\r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [width]=\"field.width\"\r\n [nullable]=\"field.nullable\"\r\n [options]=\"field.options\"\r\n [masterOptions]=\"field.masterOptions\"\r\n [masterField]=\"field.masterField\"\r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\"\r\n [optionValue]=\"field.optionValue ?? 'value'\"\r\n [(value)]=\"data[field.name]\"\r\n [defaultFirstValue]=\"field.defaultFirstValue\"\r\n [required]=\"testRequired(field)\"\r\n [readonly]=\"testReadOnly(field)\"\r\n [hint]=\"field.hint\"\r\n \r\n [loadAction]=\"field.loadAction\"\r\n [loadIDField]=\"field.loadIDField\"\r\n [field]=\"field\"\r\n [data]=\"data\"\r\n [infoMessage]=\"field.infoMessage\"\r\n [copyContent]=\"field.copyContent\"\r\n (valueChange)=\"selectChanged(field)\"\r\n ></spa-select-lite>\r\n </ng-template>\r\n </spa-form>\r\n\r\n <!-- \r\n Lite Dialog should not implement tabs \r\n PlaceHolder\r\n \r\n \r\n \r\n \r\n \r\n -->\r\n </div>\r\n\r\n </mat-dialog-content>\r\n\r\n\r\n </div>\r\n\r\n <mat-dialog-actions>\r\n\r\n <div>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='create' && createButton\" color=\"primary\" \r\n (click)=\"create()\" cdkFocusInitial>{{createButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='edit' && editButton\" color=\"primary\" \r\n (click)=\"edit()\" cdkFocusInitial>{{editButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <ng-container *ngFor=\"let btn of extraButtons\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && testVisible(details,btn.name)\" mat-stroked-button [disabled]=\"isProcessing || testDisabled(details,btn.name)\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\" (click)=\"custom(btn)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"btn.icon\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\">{{btn.icon.name}}</mat-icon>\r\n {{btn.display ?? btn.name | titlecase}}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Changed: Bottom close button now uses conditional display and custom text -->\r\n <button *ngIf=\"shouldShowBottomClose()\" mat-stroked-button color=\"primary\" (click)=\"close()\">{{getCloseText()}}</button>\r\n\r\n </div>\r\n\r\n <div class=\"col d-flex justify-content-end\" *ngIf=\"smallScreen\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Delete\" [disabled]=\"isProcessing\" style=\"color: red;\" (click)=\"delete()\" *ngIf=\"formConfig.mode!='create' && deleteButton\"><mat-icon>delete</mat-icon></button>\r\n </div>\r\n\r\n\r\n </mat-dialog-actions>\r\n\r\n\r\n</div>\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"] }]
11155
+ args: [{ selector: 'spa-detailsDialog-lite', standalone: false, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <spa-check *ngIf=\"detailsConfig.autoRefreshConfig\" display=\"Auto Refresh\" [(value)]=\"autoRefreshEnabled\" (valueChange)=\"toggleAutoRefresh()\"></spa-check>\r\n <!-- Added: Keep Open checkbox \u2014 visible when developer enables allowUserKeepOpen -->\r\n <spa-check *ngIf=\"detailsConfig.allowUserKeepOpen\" display=\"Keep Open\" [(value)]=\"userKeepOpen\"></spa-check>\r\n\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && testVisible(details,editButton.name)\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"testDisabled(details,editButton.name)\"><mat-icon>edit</mat-icon></button>\r\n </div>\r\n\r\n <button [disabled]=\"isProcessing\" *ngIf=\"loadByAction\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Refresh\" color=\"primary\" (click)=\"loadData(formConfig.loadAction, detailsConfig.causeTableRefresh)\"><mat-icon class=\"refreshIcon\">cached</mat-icon></button>\r\n \r\n <!-- Added: Top close button when position is 'top' -->\r\n <button *ngIf=\"shouldShowTopClose()\" [disabled]=\"isProcessing\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Close\" (click)=\"close()\"><mat-icon>close</mat-icon></button>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div style=\"padding-left: 24px; padding-right: 24px;\">\r\n <spa-steps *ngIf=\"stepConfig && details && stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n </div>\r\n\r\n <mat-dialog-content class=\"mat-typography dialog-scroll-content\">\r\n\r\n <spa-steps *ngIf=\"stepConfig && details && !stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && !statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && !formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n\r\n <div class=\"tin-input\" style=\"font-size:14px\">\r\n\r\n <p *ngIf=\"formConfig && !details\"><em>Loading...</em></p>\r\n\r\n <spa-form *ngIf=\"formConfig && details\" [files]=\"files\" [data]=\"details\" [config]=\"formConfig\" (inputChange)=\"inputChanged($event)\">\r\n <ng-template #dynamicSelect let-field=\"field\" let-data=\"data\" let-testReadOnly=\"testReadOnly\" let-testRequired=\"testRequired\" let-selectChanged=\"selectChanged\">\r\n <spa-select-lite\r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [width]=\"field.width\"\r\n [nullable]=\"field.nullable\"\r\n [options]=\"field.options\"\r\n [masterOptions]=\"field.masterOptions\"\r\n [masterField]=\"field.masterField\"\r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\"\r\n [optionValue]=\"field.optionValue ?? 'value'\"\r\n [(value)]=\"data[field.name]\"\r\n [defaultFirstValue]=\"field.defaultFirstValue\"\r\n [required]=\"testRequired(field)\"\r\n [readonly]=\"testReadOnly(field)\"\r\n [hint]=\"field.hint\"\r\n \r\n [loadAction]=\"field.loadAction\"\r\n [loadIDField]=\"field.loadIDField\"\r\n [field]=\"field\"\r\n [data]=\"data\"\r\n [infoMessage]=\"field.infoMessage\"\r\n [copyContent]=\"field.copyContent\"\r\n (valueChange)=\"selectChanged(field)\"\r\n ></spa-select-lite>\r\n </ng-template>\r\n </spa-form>\r\n\r\n <!-- \r\n Lite Dialog should not implement tabs \r\n PlaceHolder\r\n \r\n \r\n \r\n \r\n \r\n -->\r\n </div>\r\n\r\n </mat-dialog-content>\r\n\r\n\r\n </div>\r\n\r\n <mat-dialog-actions>\r\n\r\n <div>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='create' && createButton\" color=\"primary\" \r\n (click)=\"create()\" cdkFocusInitial>{{createButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='edit' && editButton\" color=\"primary\" \r\n (click)=\"edit()\" cdkFocusInitial>{{editButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <ng-container *ngFor=\"let btn of extraButtons\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && testVisible(details,btn.name)\" mat-stroked-button [disabled]=\"isProcessing || testDisabled(details,btn.name)\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\" (click)=\"custom(btn)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"btn.icon\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\">{{btn.icon.name}}</mat-icon>\r\n {{btn.display ?? btn.name | titlecase}}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Changed: Bottom close button now uses conditional display and custom text -->\r\n <button *ngIf=\"shouldShowBottomClose()\" mat-stroked-button color=\"primary\" (click)=\"close()\">{{getCloseText()}}</button>\r\n\r\n </div>\r\n\r\n <div class=\"col d-flex justify-content-end\" *ngIf=\"smallScreen\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Delete\" [disabled]=\"isProcessing\" style=\"color: red;\" (click)=\"delete()\" *ngIf=\"formConfig.mode!='create' && deleteButton\"><mat-icon>delete</mat-icon></button>\r\n </div>\r\n\r\n\r\n </mat-dialog-actions>\r\n\r\n\r\n</div>\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"] }]
10992
11156
  }], ctorParameters: () => [{ type: i1$3.BreakpointObserver }, { type: LoaderService }, { type: DataServiceLib }, { type: MessageService }, { type: i1.MatDialogRef }, { type: DetailsDialogConfig, decorators: [{
10993
11157
  type: Inject,
10994
11158
  args: [MAT_DIALOG_DATA]
@@ -11260,11 +11424,11 @@ class TableHeaderComponent {
11260
11424
  return this.buttonService.getButtonColor(button, row);
11261
11425
  }
11262
11426
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TableHeaderComponent, deps: [{ token: ButtonService }, { token: DialogService }, { token: MessageService }, { token: CsvService }], target: i0.ɵɵFactoryTarget.Component }); }
11263
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TableHeaderComponent, isStandalone: false, selector: "app-table-header", inputs: { lastSearch: "lastSearch", config: "config", hideTitle: "hideTitle", tableDataSource: "tableDataSource", tileConfig: "tileConfig", smallScreen: "smallScreen", tileReload: "tileReload", showFilterButton: "showFilterButton", data: "data", tileData: "tileData" }, outputs: { createClick: "createClick", customClick: "customClick", refreshClick: "refreshClick", tileClick: "tileClick", tileUnClick: "tileUnClick" }, ngImport: i0, template: "<!--Tiles Top Position -->\r\n<ng-container *ngIf=\"config.tileConfig && !smallScreen && config.tileConfig.headerPosition == 'top'\">\r\n <spa-tiles [config]=\"config.tileConfig\" [reload]=\"tileReload\" [data]=\"tileData\" [lastSearch]=\"lastSearch\" (tileClick)=\"onTileClick($event)\" (tileUnClick)=\"onTileUnClick($event)\"></spa-tiles>\r\n</ng-container>\r\n\r\n<div class=\"top\">\r\n\r\n <!-- buttons -->\r\n <div class=\"tin-row\" style=\"margin-right: 10px;\" *ngIf=\"getHeaderButtons().length > 0\" >\r\n\r\n <ng-container *ngFor=\"let button of getHeaderButtons()\">\r\n <ng-container *ngIf=\"testVisible(button)\" >\r\n <button *ngIf=\"!config.flatButtons\" mat-raised-button color=\"primary\" [disabled]=\"testDisabled(button)\" (click)=\"onButtonClick(button)\" >\r\n {{button.display}}\r\n </button>\r\n <button *ngIf=\"config.flatButtons\" mat-stroked-button [disabled]=\"testDisabled(button)\" (click)=\"onButtonClick(button)\" [ngStyle]=\"{'color': getButtonColor(button, config.parentData)}\">\r\n {{button.display}}\r\n </button>\r\n </ng-container>\r\n </ng-container>\r\n\r\n </div>\r\n <div *ngIf=\"getHeaderButtons().length == 0 && config.holdHeaderButtonSpace || getHeaderButtons().length == 0 && !config.tileConfig\"></div>\r\n\r\n <!-- tiles -->\r\n <div *ngIf=\"config.tileConfig && !smallScreen && (!config.tileConfig.headerPosition || config.tileConfig.headerPosition == 'middle')\" style=\"min-width: 75%;\">\r\n <spa-tiles [config]=\"config.tileConfig\" [reload]=\"tileReload\" [data]=\"tileData\" [lastSearch]=\"lastSearch\" (tileClick)=\"onTileClick($event)\" (tileUnClick)=\"onTileUnClick($event)\"></spa-tiles>\r\n </div>\r\n\r\n <!-- filter -->\r\n <div class=\"tin-row d-flex justify-content-end\" >\r\n\r\n <button *ngIf=\"config.download && testVisibleHeaderButton(config.download)\" mat-icon-button class=\"header-action-btn\" matTooltip=\"Download\" matTooltipPosition=\"above\" color=\"primary\" (click)=\"onDownloadClick()\">\r\n <mat-icon>download</mat-icon>\r\n </button>\r\n\r\n <button *ngIf=\"config.upload && testVisibleHeaderButton(config.upload)\" mat-icon-button class=\"header-action-btn\" matTooltip=\"Upload\" matTooltipPosition=\"above\" color=\"primary\" (click)=\"onUploadClick()\">\r\n <mat-icon>upload</mat-icon>\r\n </button>\r\n\r\n <div *ngIf=\"config.showFilter\" >\r\n <spa-filter [showText]=\"!smallScreen || (smallScreen && tableDataSource?.data?.length > 10)\" [showButton]=\"showFilterButton\" [data]=\"tableDataSource\" [flatButtons]=\"config.flatButtons\" [smallScreen]=\"smallScreen\" (refreshClick)=\"onRefreshClick()\"></spa-filter> <!-- Changed: Use .data.length for MatTableDataSource, pass smallScreen for compact width -->\r\n </div>\r\n <div *ngIf=\"!config.showFilter && config.holdFilterSpace\"></div>\r\n </div>\r\n\r\n\r\n</div>\r\n\r\n<div *ngIf=\"config.tileConfig && smallScreen\" style=\"width: 100%;\">\r\n <spa-tiles [config]=\"config.tileConfig\" [reload]=\"tileReload\" [data]=\"tileData\"[lastSearch]=\"lastSearch\" (tileClick)=\"onTileClick($event)\" (tileUnClick)=\"onTileUnClick($event)\"></spa-tiles>\r\n</div>\r\n\r\n<!-- <div *ngIf=\"config.title && !hideTitle\" class=\"title\">\r\n {{config.title | camelToWords}}\r\n</div> -->\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;row-gap:8px;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.header-action-btn{width:20px!important;height:20px!important}.header-action-btn mat-icon{font-size:18px!important;margin-top:0!important}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#e5e5e5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.tin-row{display:flex;align-items:center}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: FilterComponent, selector: "spa-filter", inputs: ["flatButtons", "showText", "showButton", "smallScreen", "data"], outputs: ["refreshClick"] }, { kind: "component", type: TilesComponent, selector: "spa-tiles", inputs: ["config", "lastSearch", "data", "reload"], outputs: ["tileActionSelected", "tileClick", "tileUnClick"] }] }); }
11427
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TableHeaderComponent, isStandalone: false, selector: "app-table-header", inputs: { lastSearch: "lastSearch", config: "config", hideTitle: "hideTitle", tableDataSource: "tableDataSource", tileConfig: "tileConfig", smallScreen: "smallScreen", tileReload: "tileReload", showFilterButton: "showFilterButton", data: "data", tileData: "tileData" }, outputs: { createClick: "createClick", customClick: "customClick", refreshClick: "refreshClick", tileClick: "tileClick", tileUnClick: "tileUnClick" }, ngImport: i0, template: "<!--Tiles Top Position -->\r\n<ng-container *ngIf=\"config.tileConfig && !smallScreen && config.tileConfig.headerPosition == 'top'\">\r\n <spa-tiles [config]=\"config.tileConfig\" [reload]=\"tileReload\" [data]=\"tileData\" [lastSearch]=\"lastSearch\" (tileClick)=\"onTileClick($event)\" (tileUnClick)=\"onTileUnClick($event)\"></spa-tiles>\r\n</ng-container>\r\n\r\n<!-- Changed: Small screen tiles render above header buttons/filter -->\r\n<div *ngIf=\"config.tileConfig && smallScreen\" style=\"width: 100%;\">\r\n <spa-tiles [config]=\"config.tileConfig\" [reload]=\"tileReload\" [data]=\"tileData\" [lastSearch]=\"lastSearch\" (tileClick)=\"onTileClick($event)\" (tileUnClick)=\"onTileUnClick($event)\"></spa-tiles>\r\n</div>\r\n\r\n<div class=\"top\">\r\n\r\n <!-- buttons -->\r\n <div class=\"tin-row\" style=\"margin-right: 10px;\" *ngIf=\"getHeaderButtons().length > 0\" >\r\n\r\n <ng-container *ngFor=\"let button of getHeaderButtons()\">\r\n <ng-container *ngIf=\"testVisible(button)\" >\r\n <button *ngIf=\"!config.flatButtons\" mat-raised-button color=\"primary\" [disabled]=\"testDisabled(button)\" (click)=\"onButtonClick(button)\" >\r\n {{button.display}}\r\n </button>\r\n <button *ngIf=\"config.flatButtons\" mat-stroked-button [disabled]=\"testDisabled(button)\" (click)=\"onButtonClick(button)\" [ngStyle]=\"{'color': getButtonColor(button, config.parentData)}\">\r\n {{button.display}}\r\n </button>\r\n </ng-container>\r\n </ng-container>\r\n\r\n </div>\r\n <div *ngIf=\"getHeaderButtons().length == 0 && config.holdHeaderButtonSpace || getHeaderButtons().length == 0 && !config.tileConfig\"></div>\r\n\r\n <!-- tiles -->\r\n <div *ngIf=\"config.tileConfig && !smallScreen && (!config.tileConfig.headerPosition || config.tileConfig.headerPosition == 'middle')\" style=\"min-width: 75%;\">\r\n <spa-tiles [config]=\"config.tileConfig\" [reload]=\"tileReload\" [data]=\"tileData\" [lastSearch]=\"lastSearch\" (tileClick)=\"onTileClick($event)\" (tileUnClick)=\"onTileUnClick($event)\"></spa-tiles>\r\n </div>\r\n\r\n <!-- filter -->\r\n <div class=\"tin-row d-flex justify-content-end\" >\r\n\r\n <button *ngIf=\"config.download && testVisibleHeaderButton(config.download)\" mat-icon-button class=\"header-action-btn\" matTooltip=\"Download\" matTooltipPosition=\"above\" color=\"primary\" (click)=\"onDownloadClick()\">\r\n <mat-icon>download</mat-icon>\r\n </button>\r\n\r\n <button *ngIf=\"config.upload && testVisibleHeaderButton(config.upload)\" mat-icon-button class=\"header-action-btn\" matTooltip=\"Upload\" matTooltipPosition=\"above\" color=\"primary\" (click)=\"onUploadClick()\">\r\n <mat-icon>upload</mat-icon>\r\n </button>\r\n\r\n <div *ngIf=\"config.showFilter\" >\r\n <spa-filter [showText]=\"!smallScreen || (smallScreen && tableDataSource?.data?.length > 10)\" [showButton]=\"showFilterButton\" [data]=\"tableDataSource\" [flatButtons]=\"config.flatButtons\" [smallScreen]=\"smallScreen\" (refreshClick)=\"onRefreshClick()\"></spa-filter> <!-- Changed: Use .data.length for MatTableDataSource, pass smallScreen for compact width -->\r\n </div>\r\n <div *ngIf=\"!config.showFilter && config.holdFilterSpace\"></div>\r\n </div>\r\n\r\n\r\n</div>\r\n\r\n<!-- <div *ngIf=\"config.title && !hideTitle\" class=\"title\">\r\n {{config.title | camelToWords}}\r\n</div> -->\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;row-gap:8px;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.header-action-btn{width:20px!important;height:20px!important}.header-action-btn mat-icon{font-size:18px!important;margin-top:0!important}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#e5e5e5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.tin-row{display:flex;align-items:center}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: FilterComponent, selector: "spa-filter", inputs: ["flatButtons", "showText", "showButton", "smallScreen", "data"], outputs: ["refreshClick"] }, { kind: "component", type: TilesComponent, selector: "spa-tiles", inputs: ["config", "lastSearch", "data", "reload"], outputs: ["tileActionSelected", "tileClick", "tileUnClick"] }] }); }
11264
11428
  }
11265
11429
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TableHeaderComponent, decorators: [{
11266
11430
  type: Component,
11267
- args: [{ selector: 'app-table-header', standalone: false, template: "<!--Tiles Top Position -->\r\n<ng-container *ngIf=\"config.tileConfig && !smallScreen && config.tileConfig.headerPosition == 'top'\">\r\n <spa-tiles [config]=\"config.tileConfig\" [reload]=\"tileReload\" [data]=\"tileData\" [lastSearch]=\"lastSearch\" (tileClick)=\"onTileClick($event)\" (tileUnClick)=\"onTileUnClick($event)\"></spa-tiles>\r\n</ng-container>\r\n\r\n<div class=\"top\">\r\n\r\n <!-- buttons -->\r\n <div class=\"tin-row\" style=\"margin-right: 10px;\" *ngIf=\"getHeaderButtons().length > 0\" >\r\n\r\n <ng-container *ngFor=\"let button of getHeaderButtons()\">\r\n <ng-container *ngIf=\"testVisible(button)\" >\r\n <button *ngIf=\"!config.flatButtons\" mat-raised-button color=\"primary\" [disabled]=\"testDisabled(button)\" (click)=\"onButtonClick(button)\" >\r\n {{button.display}}\r\n </button>\r\n <button *ngIf=\"config.flatButtons\" mat-stroked-button [disabled]=\"testDisabled(button)\" (click)=\"onButtonClick(button)\" [ngStyle]=\"{'color': getButtonColor(button, config.parentData)}\">\r\n {{button.display}}\r\n </button>\r\n </ng-container>\r\n </ng-container>\r\n\r\n </div>\r\n <div *ngIf=\"getHeaderButtons().length == 0 && config.holdHeaderButtonSpace || getHeaderButtons().length == 0 && !config.tileConfig\"></div>\r\n\r\n <!-- tiles -->\r\n <div *ngIf=\"config.tileConfig && !smallScreen && (!config.tileConfig.headerPosition || config.tileConfig.headerPosition == 'middle')\" style=\"min-width: 75%;\">\r\n <spa-tiles [config]=\"config.tileConfig\" [reload]=\"tileReload\" [data]=\"tileData\" [lastSearch]=\"lastSearch\" (tileClick)=\"onTileClick($event)\" (tileUnClick)=\"onTileUnClick($event)\"></spa-tiles>\r\n </div>\r\n\r\n <!-- filter -->\r\n <div class=\"tin-row d-flex justify-content-end\" >\r\n\r\n <button *ngIf=\"config.download && testVisibleHeaderButton(config.download)\" mat-icon-button class=\"header-action-btn\" matTooltip=\"Download\" matTooltipPosition=\"above\" color=\"primary\" (click)=\"onDownloadClick()\">\r\n <mat-icon>download</mat-icon>\r\n </button>\r\n\r\n <button *ngIf=\"config.upload && testVisibleHeaderButton(config.upload)\" mat-icon-button class=\"header-action-btn\" matTooltip=\"Upload\" matTooltipPosition=\"above\" color=\"primary\" (click)=\"onUploadClick()\">\r\n <mat-icon>upload</mat-icon>\r\n </button>\r\n\r\n <div *ngIf=\"config.showFilter\" >\r\n <spa-filter [showText]=\"!smallScreen || (smallScreen && tableDataSource?.data?.length > 10)\" [showButton]=\"showFilterButton\" [data]=\"tableDataSource\" [flatButtons]=\"config.flatButtons\" [smallScreen]=\"smallScreen\" (refreshClick)=\"onRefreshClick()\"></spa-filter> <!-- Changed: Use .data.length for MatTableDataSource, pass smallScreen for compact width -->\r\n </div>\r\n <div *ngIf=\"!config.showFilter && config.holdFilterSpace\"></div>\r\n </div>\r\n\r\n\r\n</div>\r\n\r\n<div *ngIf=\"config.tileConfig && smallScreen\" style=\"width: 100%;\">\r\n <spa-tiles [config]=\"config.tileConfig\" [reload]=\"tileReload\" [data]=\"tileData\"[lastSearch]=\"lastSearch\" (tileClick)=\"onTileClick($event)\" (tileUnClick)=\"onTileUnClick($event)\"></spa-tiles>\r\n</div>\r\n\r\n<!-- <div *ngIf=\"config.title && !hideTitle\" class=\"title\">\r\n {{config.title | camelToWords}}\r\n</div> -->\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;row-gap:8px;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.header-action-btn{width:20px!important;height:20px!important}.header-action-btn mat-icon{font-size:18px!important;margin-top:0!important}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#e5e5e5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.tin-row{display:flex;align-items:center}\n"] }]
11431
+ args: [{ selector: 'app-table-header', standalone: false, template: "<!--Tiles Top Position -->\r\n<ng-container *ngIf=\"config.tileConfig && !smallScreen && config.tileConfig.headerPosition == 'top'\">\r\n <spa-tiles [config]=\"config.tileConfig\" [reload]=\"tileReload\" [data]=\"tileData\" [lastSearch]=\"lastSearch\" (tileClick)=\"onTileClick($event)\" (tileUnClick)=\"onTileUnClick($event)\"></spa-tiles>\r\n</ng-container>\r\n\r\n<!-- Changed: Small screen tiles render above header buttons/filter -->\r\n<div *ngIf=\"config.tileConfig && smallScreen\" style=\"width: 100%;\">\r\n <spa-tiles [config]=\"config.tileConfig\" [reload]=\"tileReload\" [data]=\"tileData\" [lastSearch]=\"lastSearch\" (tileClick)=\"onTileClick($event)\" (tileUnClick)=\"onTileUnClick($event)\"></spa-tiles>\r\n</div>\r\n\r\n<div class=\"top\">\r\n\r\n <!-- buttons -->\r\n <div class=\"tin-row\" style=\"margin-right: 10px;\" *ngIf=\"getHeaderButtons().length > 0\" >\r\n\r\n <ng-container *ngFor=\"let button of getHeaderButtons()\">\r\n <ng-container *ngIf=\"testVisible(button)\" >\r\n <button *ngIf=\"!config.flatButtons\" mat-raised-button color=\"primary\" [disabled]=\"testDisabled(button)\" (click)=\"onButtonClick(button)\" >\r\n {{button.display}}\r\n </button>\r\n <button *ngIf=\"config.flatButtons\" mat-stroked-button [disabled]=\"testDisabled(button)\" (click)=\"onButtonClick(button)\" [ngStyle]=\"{'color': getButtonColor(button, config.parentData)}\">\r\n {{button.display}}\r\n </button>\r\n </ng-container>\r\n </ng-container>\r\n\r\n </div>\r\n <div *ngIf=\"getHeaderButtons().length == 0 && config.holdHeaderButtonSpace || getHeaderButtons().length == 0 && !config.tileConfig\"></div>\r\n\r\n <!-- tiles -->\r\n <div *ngIf=\"config.tileConfig && !smallScreen && (!config.tileConfig.headerPosition || config.tileConfig.headerPosition == 'middle')\" style=\"min-width: 75%;\">\r\n <spa-tiles [config]=\"config.tileConfig\" [reload]=\"tileReload\" [data]=\"tileData\" [lastSearch]=\"lastSearch\" (tileClick)=\"onTileClick($event)\" (tileUnClick)=\"onTileUnClick($event)\"></spa-tiles>\r\n </div>\r\n\r\n <!-- filter -->\r\n <div class=\"tin-row d-flex justify-content-end\" >\r\n\r\n <button *ngIf=\"config.download && testVisibleHeaderButton(config.download)\" mat-icon-button class=\"header-action-btn\" matTooltip=\"Download\" matTooltipPosition=\"above\" color=\"primary\" (click)=\"onDownloadClick()\">\r\n <mat-icon>download</mat-icon>\r\n </button>\r\n\r\n <button *ngIf=\"config.upload && testVisibleHeaderButton(config.upload)\" mat-icon-button class=\"header-action-btn\" matTooltip=\"Upload\" matTooltipPosition=\"above\" color=\"primary\" (click)=\"onUploadClick()\">\r\n <mat-icon>upload</mat-icon>\r\n </button>\r\n\r\n <div *ngIf=\"config.showFilter\" >\r\n <spa-filter [showText]=\"!smallScreen || (smallScreen && tableDataSource?.data?.length > 10)\" [showButton]=\"showFilterButton\" [data]=\"tableDataSource\" [flatButtons]=\"config.flatButtons\" [smallScreen]=\"smallScreen\" (refreshClick)=\"onRefreshClick()\"></spa-filter> <!-- Changed: Use .data.length for MatTableDataSource, pass smallScreen for compact width -->\r\n </div>\r\n <div *ngIf=\"!config.showFilter && config.holdFilterSpace\"></div>\r\n </div>\r\n\r\n\r\n</div>\r\n\r\n<!-- <div *ngIf=\"config.title && !hideTitle\" class=\"title\">\r\n {{config.title | camelToWords}}\r\n</div> -->\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;row-gap:8px;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.header-action-btn{width:20px!important;height:20px!important}.header-action-btn mat-icon{font-size:18px!important;margin-top:0!important}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#e5e5e5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.tin-row{display:flex;align-items:center}\n"] }]
11268
11432
  }], ctorParameters: () => [{ type: ButtonService }, { type: DialogService }, { type: MessageService }, { type: CsvService }], propDecorators: { lastSearch: [{
11269
11433
  type: Input
11270
11434
  }], config: [{
@@ -11359,11 +11523,11 @@ class TableRowComponent {
11359
11523
  return false;
11360
11524
  }
11361
11525
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TableRowComponent, deps: [{ token: ButtonService }], target: i0.ɵɵFactoryTarget.Component }); }
11362
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TableRowComponent, isStandalone: false, selector: "app-table-row", inputs: { column: "column", row: "row", config: "config", smallScreen: "smallScreen" }, outputs: { actionClick: "actionClick", columnClick: "columnClick", showBannerEvent: "showBannerEvent" }, ngImport: i0, template: "<ng-container [ngSwitch]=\"column.type\">\r\n <ng-container *ngSwitchCase=\"'checkbox'\">\r\n <spa-check [value]=\"row[column.name]\" [readonly]=\"true\"></spa-check>\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'select'\">\r\n <spa-select-lite [options]=\"column.options\" [optionDisplay]=\"column.optionDisplay\" [optionValue]=\"column.optionValue\" [(value)]=\"row[column.name]\" width=\"90%\"></spa-select-lite>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'chip'\">\r\n <button mat-stroked-button (click)=\"onColumnClick(column, row)\" [ngStyle]=\"{'background-color': getColorOnCondition(row, column), 'color': 'rgba(0, 0, 0, 0.87)', 'border': 'none'}\">{{row[column.name]}}</button>\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'icon'\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'date'\">\r\n {{row[column.name] | date : 'dd/MM/yyyy'}}\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'datetime'\">\r\n {{row[column.name] | date : 'dd/MM/yyyy HH:mm'}}\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'datetimesec'\">\r\n {{row[column.name] | date : 'dd/MM/yyyy HH:mm:ss'}}\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'money'\">\r\n <label [ngStyle]=\"{'color': getColorOnCondition(row, column) }\">{{row[column.name] | currency:'':''}}</label>\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'button'\">\r\n <button mat-stroked-button (click)=\"onColumnClick(column, row)\" [ngStyle]=\"{'color': getColorOnCondition(row, column)}\" >{{row[column.name]}}</button>\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchDefault>\r\n <label [ngStyle]=\"{'color': getColorOnCondition(row, column) }\">\r\n <ng-container *ngIf=\"column.type === 'number'\">\r\n {{row[column.name] | number:'1.0-2'}}\r\n </ng-container>\r\n <ng-container *ngIf=\"column.type !== 'number'\">\r\n {{textDisplayed(row, column)}}\r\n </ng-container>\r\n </label>\r\n <mat-icon class=\"col-icon\" *ngIf=\"textTruncated(row, column)\" matTooltip='Show more' matTooltipPosition=\"above\" (click)=\"showBanner(row[column.name])\">more_horiz</mat-icon>\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n</ng-container>\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px;vertical-align:middle}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#e5e5e5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.table-chip{font-size:14px!important;font-weight:500!important;letter-spacing:.25px!important;padding:8px 20px!important;min-height:36px!important;height:auto!important;border-radius:24px!important;cursor:pointer!important;transition:all .2s ease!important;color:#000000de!important;box-shadow:none!important;border:1.5px solid rgba(0,0,0,.2)!important;text-transform:none!important;white-space:nowrap!important;background-clip:padding-box!important}.table-chip:hover{transform:none!important;box-shadow:none!important;filter:brightness(.96)!important;border-color:#0000004d!important}.table-chip:active{transform:scale(.98)!important;box-shadow:none!important;transition-duration:.1s!important;filter:brightness(.92)!important}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i2.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i2.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i2.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: CheckComponent, selector: "spa-check", inputs: ["readonly", "display", "value", "infoMessage"], outputs: ["valueChange", "click", "check", "uncheck", "infoClick"] }, { kind: "component", type: SelectLiteComponent, selector: "spa-select-lite" }, { kind: "pipe", type: i2.DecimalPipe, name: "number" }, { kind: "pipe", type: i2.CurrencyPipe, name: "currency" }, { kind: "pipe", type: i2.DatePipe, name: "date" }] }); }
11526
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TableRowComponent, isStandalone: false, selector: "app-table-row", inputs: { column: "column", row: "row", config: "config", smallScreen: "smallScreen" }, outputs: { actionClick: "actionClick", columnClick: "columnClick", showBannerEvent: "showBannerEvent" }, ngImport: i0, template: "<ng-container [ngSwitch]=\"column.type\">\r\n <ng-container *ngSwitchCase=\"'checkbox'\">\r\n <spa-check [value]=\"row[column.name]\" [readonly]=\"true\"></spa-check>\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'select'\">\r\n <spa-select-lite [options]=\"column.options\" [optionDisplay]=\"column.optionDisplay\" [optionValue]=\"column.optionValue\" [(value)]=\"row[column.name]\" width=\"90%\"></spa-select-lite>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'chip'\">\r\n <button mat-stroked-button (click)=\"onColumnClick(column, row)\" [ngStyle]=\"{'background-color': getColorOnCondition(row, column), 'color': 'rgba(0, 0, 0, 0.87)', 'border': 'none'}\">{{row[column.name]}}</button>\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'icon'\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'date'\">\r\n {{row[column.name] | date : 'dd/MM/yyyy'}}\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'datetime'\">\r\n {{row[column.name] | date : 'dd/MM/yyyy HH:mm'}}\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'datetimesec'\">\r\n {{row[column.name] | date : 'dd/MM/yyyy HH:mm:ss'}}\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'money'\">\r\n <label [ngStyle]=\"{'color': getColorOnCondition(row, column) }\">{{row[column.name] | currency:'':''}}</label>\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'button'\">\r\n <button mat-stroked-button (click)=\"onColumnClick(column, row)\" [ngStyle]=\"{'color': getColorOnCondition(row, column)}\" >{{row[column.name]}}</button>\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchDefault>\r\n <label [ngStyle]=\"{'color': getColorOnCondition(row, column) }\">\r\n <ng-container *ngIf=\"column.type === 'number'\">\r\n {{row[column.name] | number:'1.0-2'}}\r\n </ng-container>\r\n <ng-container *ngIf=\"column.type !== 'number'\">\r\n {{textDisplayed(row, column)}}\r\n </ng-container>\r\n </label>\r\n <mat-icon class=\"col-icon\" *ngIf=\"textTruncated(row, column)\" matTooltip='Show more' matTooltipPosition=\"above\" (click)=\"showBanner(row[column.name])\">more_horiz</mat-icon>\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n</ng-container>\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px;vertical-align:middle}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#e5e5e5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.mat-mdc-cell .mat-mdc-outlined-button,.mat-mdc-cell .mat-mdc-button-base:not(.mat-mdc-icon-button){height:auto!important;min-height:36px!important;white-space:normal!important;word-break:break-word!important;overflow-wrap:break-word!important;line-height:1.4!important;text-align:left!important;padding:6px 16px!important}.mat-mdc-cell .mat-mdc-outlined-button .mdc-button__label,.mat-mdc-cell .mat-mdc-button-base:not(.mat-mdc-icon-button) .mdc-button__label{white-space:normal!important;word-break:break-word!important;overflow-wrap:break-word!important}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i2.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i2.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i2.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: CheckComponent, selector: "spa-check", inputs: ["readonly", "display", "value", "infoMessage"], outputs: ["valueChange", "click", "check", "uncheck", "infoClick"] }, { kind: "component", type: SelectLiteComponent, selector: "spa-select-lite" }, { kind: "pipe", type: i2.DecimalPipe, name: "number" }, { kind: "pipe", type: i2.CurrencyPipe, name: "currency" }, { kind: "pipe", type: i2.DatePipe, name: "date" }], encapsulation: i0.ViewEncapsulation.None }); }
11363
11527
  }
11364
11528
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TableRowComponent, decorators: [{
11365
11529
  type: Component,
11366
- args: [{ selector: 'app-table-row', standalone: false, template: "<ng-container [ngSwitch]=\"column.type\">\r\n <ng-container *ngSwitchCase=\"'checkbox'\">\r\n <spa-check [value]=\"row[column.name]\" [readonly]=\"true\"></spa-check>\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'select'\">\r\n <spa-select-lite [options]=\"column.options\" [optionDisplay]=\"column.optionDisplay\" [optionValue]=\"column.optionValue\" [(value)]=\"row[column.name]\" width=\"90%\"></spa-select-lite>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'chip'\">\r\n <button mat-stroked-button (click)=\"onColumnClick(column, row)\" [ngStyle]=\"{'background-color': getColorOnCondition(row, column), 'color': 'rgba(0, 0, 0, 0.87)', 'border': 'none'}\">{{row[column.name]}}</button>\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'icon'\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'date'\">\r\n {{row[column.name] | date : 'dd/MM/yyyy'}}\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'datetime'\">\r\n {{row[column.name] | date : 'dd/MM/yyyy HH:mm'}}\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'datetimesec'\">\r\n {{row[column.name] | date : 'dd/MM/yyyy HH:mm:ss'}}\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'money'\">\r\n <label [ngStyle]=\"{'color': getColorOnCondition(row, column) }\">{{row[column.name] | currency:'':''}}</label>\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'button'\">\r\n <button mat-stroked-button (click)=\"onColumnClick(column, row)\" [ngStyle]=\"{'color': getColorOnCondition(row, column)}\" >{{row[column.name]}}</button>\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchDefault>\r\n <label [ngStyle]=\"{'color': getColorOnCondition(row, column) }\">\r\n <ng-container *ngIf=\"column.type === 'number'\">\r\n {{row[column.name] | number:'1.0-2'}}\r\n </ng-container>\r\n <ng-container *ngIf=\"column.type !== 'number'\">\r\n {{textDisplayed(row, column)}}\r\n </ng-container>\r\n </label>\r\n <mat-icon class=\"col-icon\" *ngIf=\"textTruncated(row, column)\" matTooltip='Show more' matTooltipPosition=\"above\" (click)=\"showBanner(row[column.name])\">more_horiz</mat-icon>\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n</ng-container>\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px;vertical-align:middle}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#e5e5e5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.table-chip{font-size:14px!important;font-weight:500!important;letter-spacing:.25px!important;padding:8px 20px!important;min-height:36px!important;height:auto!important;border-radius:24px!important;cursor:pointer!important;transition:all .2s ease!important;color:#000000de!important;box-shadow:none!important;border:1.5px solid rgba(0,0,0,.2)!important;text-transform:none!important;white-space:nowrap!important;background-clip:padding-box!important}.table-chip:hover{transform:none!important;box-shadow:none!important;filter:brightness(.96)!important;border-color:#0000004d!important}.table-chip:active{transform:scale(.98)!important;box-shadow:none!important;transition-duration:.1s!important;filter:brightness(.92)!important}\n"] }]
11530
+ args: [{ selector: 'app-table-row', encapsulation: ViewEncapsulation.None, standalone: false, template: "<ng-container [ngSwitch]=\"column.type\">\r\n <ng-container *ngSwitchCase=\"'checkbox'\">\r\n <spa-check [value]=\"row[column.name]\" [readonly]=\"true\"></spa-check>\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'select'\">\r\n <spa-select-lite [options]=\"column.options\" [optionDisplay]=\"column.optionDisplay\" [optionValue]=\"column.optionValue\" [(value)]=\"row[column.name]\" width=\"90%\"></spa-select-lite>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'chip'\">\r\n <button mat-stroked-button (click)=\"onColumnClick(column, row)\" [ngStyle]=\"{'background-color': getColorOnCondition(row, column), 'color': 'rgba(0, 0, 0, 0.87)', 'border': 'none'}\">{{row[column.name]}}</button>\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'icon'\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'date'\">\r\n {{row[column.name] | date : 'dd/MM/yyyy'}}\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'datetime'\">\r\n {{row[column.name] | date : 'dd/MM/yyyy HH:mm'}}\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'datetimesec'\">\r\n {{row[column.name] | date : 'dd/MM/yyyy HH:mm:ss'}}\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'money'\">\r\n <label [ngStyle]=\"{'color': getColorOnCondition(row, column) }\">{{row[column.name] | currency:'':''}}</label>\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'button'\">\r\n <button mat-stroked-button (click)=\"onColumnClick(column, row)\" [ngStyle]=\"{'color': getColorOnCondition(row, column)}\" >{{row[column.name]}}</button>\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchDefault>\r\n <label [ngStyle]=\"{'color': getColorOnCondition(row, column) }\">\r\n <ng-container *ngIf=\"column.type === 'number'\">\r\n {{row[column.name] | number:'1.0-2'}}\r\n </ng-container>\r\n <ng-container *ngIf=\"column.type !== 'number'\">\r\n {{textDisplayed(row, column)}}\r\n </ng-container>\r\n </label>\r\n <mat-icon class=\"col-icon\" *ngIf=\"textTruncated(row, column)\" matTooltip='Show more' matTooltipPosition=\"above\" (click)=\"showBanner(row[column.name])\">more_horiz</mat-icon>\r\n <mat-icon class=\"col-icon\" *ngIf=\"column.icon && testIconCondition(row, column.icon)\" [matTooltip]=\"row[column.icon.tipField] ?? column.icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[column.icon.tipField])\" [ngStyle]=\"{'color':column.icon?.color}\">{{column.icon.name }}</mat-icon>\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon class=\"col-icon\" *ngIf=\"testIconCondition(row, icon)\" [matTooltip]=\"row[icon.tipField] ?? icon?.tip\" matTooltipPosition=\"above\" (click)=\"showBanner(row[icon.tipField])\" [ngStyle]=\"{'color':icon?.color}\">{{icon.name }}</mat-icon>\r\n </ng-container>\r\n </ng-container>\r\n</ng-container>\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px;vertical-align:middle}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#e5e5e5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.mat-mdc-cell .mat-mdc-outlined-button,.mat-mdc-cell .mat-mdc-button-base:not(.mat-mdc-icon-button){height:auto!important;min-height:36px!important;white-space:normal!important;word-break:break-word!important;overflow-wrap:break-word!important;line-height:1.4!important;text-align:left!important;padding:6px 16px!important}.mat-mdc-cell .mat-mdc-outlined-button .mdc-button__label,.mat-mdc-cell .mat-mdc-button-base:not(.mat-mdc-icon-button) .mdc-button__label{white-space:normal!important;word-break:break-word!important;overflow-wrap:break-word!important}\n"] }]
11367
11531
  }], ctorParameters: () => [{ type: ButtonService }], propDecorators: { column: [{
11368
11532
  type: Input
11369
11533
  }], row: [{
@@ -12130,12 +12294,16 @@ class TableLiteComponent {
12130
12294
  const buttonName = button.name;
12131
12295
  let btn = {
12132
12296
  ...button,
12133
- detailsConfig: button.detailsConfig ? button.detailsConfig : {
12134
- formConfig: this.config.formConfig,
12135
- stepConfig: this.config.stepConfig,
12136
- buttons: this.config.buttons,
12137
- heroField: this.config.heroField,
12138
- heroValue: this.config.heroValue,
12297
+ detailsConfig: {
12298
+ ...(button.detailsConfig ?? {
12299
+ formConfig: this.config.formConfig,
12300
+ stepConfig: this.config.stepConfig,
12301
+ buttons: this.config.buttons,
12302
+ heroField: this.config.heroField,
12303
+ heroValue: this.config.heroValue,
12304
+ }),
12305
+ allowUserKeepOpen: this.config.allowUserKeepOpen, // Changed: table config is the single source of truth for this setting
12306
+ keepOpenBehavior: this.config.keepOpenBehavior, // Changed: propagate create behavior preference from table config
12139
12307
  }
12140
12308
  };
12141
12309
  this.dialogService.openConfiguredDetailsDialog(btn, row, DetailsDialogLite).subscribe(result => {
@@ -12245,7 +12413,7 @@ class TableLiteComponent {
12245
12413
  }
12246
12414
  }
12247
12415
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TableLiteComponent, deps: [{ token: DataServiceLib }, { token: MessageService }, { token: i1$3.BreakpointObserver }, { token: i1.MatDialog }, { token: ButtonService }, { token: DialogService }, { token: TableConfigService }, { token: ConditionService }, { token: AuthService }], target: i0.ɵɵFactoryTarget.Component }); }
12248
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TableLiteComponent, isStandalone: false, selector: "spa-table-lite", inputs: { data: "data", tileData: "tileData", config: "config", reload: "reload", activeTab: "activeTab", inTab: "inTab" }, outputs: { dataLoad: "dataLoad", actionSuccess: "actionSuccess", refreshClick: "refreshClick", searchClick: "searchClick", createClick: "createClick", actionClick: "actionClick", inputChange: "inputChange", actionResponse: "actionResponse" }, viewQueries: [{ propertyName: "tablePaginator", first: true, predicate: ["tablePaginator"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "\r\n<ng-container *ngIf=\"hasFormAccess\">\r\n\r\n <!-- Search -->\r\n <spa-search\r\n *ngIf=\"config.searchConfig\" [config]=\"config.searchConfig\" [smallScreen]=\"smallScreen\" [tableDataSource]=\"tableDataSource\" style=\"margin-bottom: 20px;\" (searchClick)=\"searchClicked($event)\">\r\n </spa-search>\r\n\r\n <!-- Header -->\r\n <app-table-header\r\n [config]=\"config\" [data]=\"dataSource\" [tableDataSource]=\"tableDataSource\" [tileConfig]=\"config.tileConfig\" [tileData]=\"tileData\" [tileReload]=\"tileReload\" [lastSearch]=\"lastSearch\" [smallScreen]=\"smallScreen\"\r\n [showFilterButton]=\"showFilterButton\" (createClick)=\"newModel()\" (customClick)=\"customModel($event,null)\"\r\n (refreshClick)=\"refreshClicked()\" (tileClick)=\"tileClicked($event)\" (tileUnClick)=\"tileUnClicked($event)\">\r\n </app-table-header>\r\n\r\n\r\n <!-- Table -->\r\n <div *ngIf=\"!config.viewType || config?.viewType === 'table'\">\r\n\r\n <p *ngIf=\"!config\"><em>Configure Table</em></p>\r\n <p *ngIf=\"!dataSource\"><em>Loading...</em></p>\r\n\r\n <div *ngIf=\"dataSource && (!smallScreen || (smallScreen && dataSource?.length > 0))\">\r\n\r\n <table mat-table [dataSource]=\"tableDataSource\" [ngClass]=\"elevation\">\r\n\r\n <ng-container *ngFor=\"let column of config.columns\" [matColumnDef]=\"column.name\">\r\n <th mat-header-cell *matHeaderCellDef >{{ column.alias ?? column.name | camelToWords }}</th>\r\n <td mat-cell *matCellDef=\"let row;\" class=\"right-padding\" >\r\n\r\n <!-- Rows -->\r\n <app-table-row [column]=\"column\" [row]=\"row\" [config]=\"config\" [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked(column.name, row)\" (columnClick)=\"columnClicked(column, row)\" (showBannerEvent)=\"showBanner($event)\">\r\n </app-table-row>\r\n\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"action\">\r\n <th mat-header-cell *matHeaderCellDef> Action </th>\r\n <td mat-cell *matCellDef=\"let row\" [ngStyle]=\"{width:false ? '50px' : actionsWidth}\">\r\n <div class=\"action-buttons-container\">\r\n\r\n <!-- Actions -->\r\n <app-table-action\r\n [displayedButtons]=\"displayedButtons\" [config]=\"config\" [smallScreen]=\"smallScreen\" [row]=\"row\" (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </app-table-action>\r\n\r\n </div>\r\n </td>\r\n </ng-container>\r\n\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" [ngClass]=\"{'make-gray': (config.greyOut && config.greyOut(row)) || row.pendingApproval}\"></tr>\r\n </table>\r\n\r\n </div>\r\n\r\n <!-- Changed: Removed *ngIf condition to keep paginator always in DOM and maintain ViewChild reference -->\r\n <!-- Changed: Added CSS class binding to hide when no data instead of conditional rendering -->\r\n <mat-paginator \r\n #tablePaginator \r\n [pageSizeOptions]=\"config.pageSizes ?? [10, 20, 50]\" \r\n [ngClass]=\"{'paginator-hidden': !dataSource || (smallScreen && dataSource?.length === 0)}\"\r\n showFirstLastButtons>\r\n </mat-paginator>\r\n\r\n </div>\r\n \r\n <!-- Capsules -->\r\n <spa-capsules *ngIf=\"config?.viewType === 'capsule'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </spa-capsules>\r\n\r\n <!-- Cards -->\r\n <spa-cards *ngIf=\"config?.viewType === 'card'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\"\r\n (columnClick)=\"columnClicked($event.column, $event.row)\"\r\n (showBannerEvent)=\"showBanner($event)\">\r\n </spa-cards>\r\n\r\n <!-- Groups -->\r\n <spa-groups *ngIf=\"config?.viewType === 'grouped'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row, $event.group, $event.button)\">\r\n </spa-groups>\r\n\r\n <div class=\"tin-center\">\r\n <p *ngIf=\"dataSource?.length == 0\"><em>{{config.noDataMessage ?? 'No Data'}}</em></p>\r\n </div>\r\n\r\n</ng-container>\r\n\r\n\r\n<ng-container *ngIf=\"!hasFormAccess\">\r\n <div class=\"tin-center\">\r\n <p><em>Access Restricted</em></p>\r\n </div>\r\n</ng-container>\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i11.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i11.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i11.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i11.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i11.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i11.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i11.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i11.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i11.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i11.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i12$2.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: SearchComponent, selector: "spa-search", inputs: ["config", "smallScreen", "tableDataSource"], outputs: ["searchClick"] }, { kind: "component", type: TableHeaderComponent, selector: "app-table-header", inputs: ["lastSearch", "config", "hideTitle", "tableDataSource", "tileConfig", "smallScreen", "tileReload", "showFilterButton", "data", "tileData"], outputs: ["createClick", "customClick", "refreshClick", "tileClick", "tileUnClick"] }, { kind: "component", type: TableRowComponent, selector: "app-table-row", inputs: ["column", "row", "config", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: TableActionComponent, selector: "app-table-action", inputs: ["displayedButtons", "config", "row", "smallScreen"], outputs: ["actionClick"] }, { kind: "component", type: CapsulesComponent, selector: "spa-capsules", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "component", type: CardsComponent, selector: "spa-cards", inputs: ["config", "dataSource", "displayedButtons", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: GroupsComponent, selector: "spa-groups", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
12416
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TableLiteComponent, isStandalone: false, selector: "spa-table-lite", inputs: { data: "data", tileData: "tileData", config: "config", reload: "reload", activeTab: "activeTab", inTab: "inTab" }, outputs: { dataLoad: "dataLoad", actionSuccess: "actionSuccess", refreshClick: "refreshClick", searchClick: "searchClick", createClick: "createClick", actionClick: "actionClick", inputChange: "inputChange", actionResponse: "actionResponse" }, viewQueries: [{ propertyName: "tablePaginator", first: true, predicate: ["tablePaginator"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "\r\n<ng-container *ngIf=\"hasFormAccess\">\r\n\r\n <!-- Search -->\r\n <spa-search\r\n *ngIf=\"config.searchConfig\" [config]=\"config.searchConfig\" [smallScreen]=\"smallScreen\" [tableDataSource]=\"tableDataSource\" style=\"margin-bottom: 20px;\" (searchClick)=\"searchClicked($event)\">\r\n </spa-search>\r\n\r\n <!-- Header -->\r\n <app-table-header\r\n [config]=\"config\" [data]=\"dataSource\" [tableDataSource]=\"tableDataSource\" [tileConfig]=\"config.tileConfig\" [tileData]=\"tileData\" [tileReload]=\"tileReload\" [lastSearch]=\"lastSearch\" [smallScreen]=\"smallScreen\"\r\n [showFilterButton]=\"showFilterButton\" (createClick)=\"newModel()\" (customClick)=\"customModel($event,null)\"\r\n (refreshClick)=\"refreshClicked()\" (tileClick)=\"tileClicked($event)\" (tileUnClick)=\"tileUnClicked($event)\">\r\n </app-table-header>\r\n\r\n\r\n <!-- Table -->\r\n <div *ngIf=\"!config.viewType || config?.viewType === 'table'\">\r\n\r\n <p *ngIf=\"!config\"><em>Configure Table</em></p>\r\n <p *ngIf=\"!dataSource\"><em>Loading...</em></p>\r\n\r\n <div *ngIf=\"dataSource && (!smallScreen || (smallScreen && dataSource?.length > 0))\">\r\n\r\n <table mat-table [dataSource]=\"tableDataSource\" [ngClass]=\"elevation\">\r\n\r\n <ng-container *ngFor=\"let column of config.columns\" [matColumnDef]=\"column.name\">\r\n <th mat-header-cell *matHeaderCellDef >{{ column.alias ?? column.name | camelToWords }}</th>\r\n <td mat-cell *matCellDef=\"let row;\" class=\"right-padding\" >\r\n\r\n <!-- Rows -->\r\n <app-table-row [column]=\"column\" [row]=\"row\" [config]=\"config\" [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked(column.name, row)\" (columnClick)=\"columnClicked(column, row)\" (showBannerEvent)=\"showBanner($event)\">\r\n </app-table-row>\r\n\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"action\">\r\n <th mat-header-cell *matHeaderCellDef> Action </th>\r\n <td mat-cell *matCellDef=\"let row\" [ngStyle]=\"{width:false ? '50px' : actionsWidth}\">\r\n <div class=\"action-buttons-container\">\r\n\r\n <!-- Actions -->\r\n <app-table-action\r\n [displayedButtons]=\"displayedButtons\" [config]=\"config\" [smallScreen]=\"smallScreen\" [row]=\"row\" (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </app-table-action>\r\n\r\n </div>\r\n </td>\r\n </ng-container>\r\n\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" [ngClass]=\"{'make-gray': (config.greyOut && config.greyOut(row)) || row.pendingApproval}\"></tr>\r\n </table>\r\n\r\n </div>\r\n\r\n <!-- Changed: Removed *ngIf condition to keep paginator always in DOM and maintain ViewChild reference -->\r\n <!-- Changed: Added CSS class binding to hide when no data instead of conditional rendering -->\r\n <mat-paginator \r\n #tablePaginator \r\n [pageSizeOptions]=\"config.pageSizes ?? [10, 20, 50]\" \r\n [ngClass]=\"{'paginator-hidden': !dataSource || (smallScreen && dataSource?.length === 0)}\"\r\n showFirstLastButtons>\r\n </mat-paginator>\r\n\r\n </div>\r\n \r\n <!-- Capsules -->\r\n <spa-capsules *ngIf=\"config?.viewType === 'capsule'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </spa-capsules>\r\n\r\n <!-- Cards -->\r\n <spa-cards *ngIf=\"config?.viewType === 'card'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\"\r\n (columnClick)=\"columnClicked($event.column, $event.row)\"\r\n (showBannerEvent)=\"showBanner($event)\">\r\n </spa-cards>\r\n\r\n <!-- Groups -->\r\n <spa-groups *ngIf=\"config?.viewType === 'grouped'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row, $event.group, $event.button)\">\r\n </spa-groups>\r\n\r\n <div class=\"tin-center\">\r\n <p *ngIf=\"dataSource?.length == 0\"><em>{{config.noDataMessage ?? 'No Data'}}</em></p>\r\n </div>\r\n\r\n</ng-container>\r\n\r\n\r\n<ng-container *ngIf=\"!hasFormAccess\">\r\n <div class=\"tin-center\">\r\n <p><em>Access Restricted</em></p>\r\n </div>\r\n</ng-container>\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i12$2.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i12$2.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i12$2.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i12$2.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i12$2.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i12$2.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i12$2.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i12$2.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i12$2.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i12$2.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i13.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: SearchComponent, selector: "spa-search", inputs: ["config", "smallScreen", "tableDataSource"], outputs: ["searchClick"] }, { kind: "component", type: TableHeaderComponent, selector: "app-table-header", inputs: ["lastSearch", "config", "hideTitle", "tableDataSource", "tileConfig", "smallScreen", "tileReload", "showFilterButton", "data", "tileData"], outputs: ["createClick", "customClick", "refreshClick", "tileClick", "tileUnClick"] }, { kind: "component", type: TableRowComponent, selector: "app-table-row", inputs: ["column", "row", "config", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: TableActionComponent, selector: "app-table-action", inputs: ["displayedButtons", "config", "row", "smallScreen"], outputs: ["actionClick"] }, { kind: "component", type: CapsulesComponent, selector: "spa-capsules", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "component", type: CardsComponent, selector: "spa-cards", inputs: ["config", "dataSource", "displayedButtons", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: GroupsComponent, selector: "spa-groups", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
12249
12417
  }
12250
12418
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TableLiteComponent, decorators: [{
12251
12419
  type: Component,
@@ -12385,6 +12553,7 @@ class DetailsDialogInternal {
12385
12553
  this.authService = authService;
12386
12554
  this.tableConfigService = tableConfigService;
12387
12555
  this.autoRefreshEnabled = false;
12556
+ this.userKeepOpen = false; // Added: runtime state for the user-controlled Keep Open checkbox
12388
12557
  this.titleAction = "View";
12389
12558
  this.loadByAction = false;
12390
12559
  this.files = [];
@@ -12600,9 +12769,15 @@ class DetailsDialogInternal {
12600
12769
  }
12601
12770
  this.processButtonAction(button);
12602
12771
  }
12772
+ // Added: returns the effective keepOpen value — user checkbox takes precedence when allowUserKeepOpen is enabled
12773
+ effectiveKeepOpen(button) {
12774
+ if (this.detailsConfig.allowUserKeepOpen)
12775
+ return this.userKeepOpen;
12776
+ return button.keepOpen ?? false;
12777
+ }
12603
12778
  processButtonAction(button) {
12604
12779
  if (!button.action) {
12605
- if (!button.keepOpen) {
12780
+ if (!this.effectiveKeepOpen(button)) { // Changed: use effectiveKeepOpen to respect user checkbox
12606
12781
  this.dialogRef.close({ message: 'emit', data: this.details });
12607
12782
  }
12608
12783
  return;
@@ -12682,8 +12857,12 @@ class DetailsDialogInternal {
12682
12857
  if (button.onSuccess) {
12683
12858
  button.onSuccess(apiResponse.data, this.details);
12684
12859
  }
12685
- if (button.keepOpen) {
12686
- if (button.name === 'create' && apiResponse.data) {
12860
+ if (this.effectiveKeepOpen(button)) { // Changed: use effectiveKeepOpen to respect user checkbox
12861
+ if (button.name === 'create' && this.detailsConfig.allowUserKeepOpen && this.userKeepOpen && this.detailsConfig.keepOpenBehavior === 'reset') {
12862
+ // Changed: User checkbox keep-open on create with 'reset' behavior — clear form for next capture, stay in create mode
12863
+ this.details = Core.generateObject(this.formConfig.fields);
12864
+ }
12865
+ else if (button.name === 'create' && apiResponse.data) {
12687
12866
  this.details = apiResponse.data;
12688
12867
  if (this.detailsConfig.heroField && apiResponse.data[this.detailsConfig.heroField]) {
12689
12868
  this.detailsConfig.heroValue = apiResponse.data[this.detailsConfig.heroField];
@@ -12728,11 +12907,11 @@ class DetailsDialogInternal {
12728
12907
  }
12729
12908
  }
12730
12909
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DetailsDialogInternal, deps: [{ token: i1$3.BreakpointObserver }, { token: LoaderService }, { token: DataServiceLib }, { token: MessageService }, { token: i1.MatDialogRef }, { token: MAT_DIALOG_DATA }, { token: ButtonService }, { token: DialogService }, { token: AuthService }, { token: TableConfigService }], target: i0.ɵɵFactoryTarget.Component }); }
12731
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: DetailsDialogInternal, isStandalone: false, selector: "spa-detailsDialog-internal", outputs: { inputChange: "inputChange" }, ngImport: i0, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <spa-check *ngIf=\"detailsConfig.autoRefreshConfig\" display=\"Auto Refresh\" [(value)]=\"autoRefreshEnabled\" (valueChange)=\"toggleAutoRefresh()\"></spa-check>\r\n\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && testVisible(details,editButton.name)\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"testDisabled(details,editButton.name)\"><mat-icon>edit</mat-icon></button>\r\n </div>\r\n\r\n <button [disabled]=\"isProcessing\" *ngIf=\"loadByAction\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Refresh\" color=\"primary\" (click)=\"loadData(formConfig.loadAction, detailsConfig.causeTableRefresh)\"><mat-icon class=\"refreshIcon\">cached</mat-icon></button>\r\n \r\n <!-- Added: Top close button when position is 'top' -->\r\n <button *ngIf=\"shouldShowTopClose()\" [disabled]=\"isProcessing\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Close\" (click)=\"close()\"><mat-icon>close</mat-icon></button>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div style=\"padding-left: 24px; padding-right: 24px;\">\r\n <spa-steps *ngIf=\"stepConfig && details && stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n </div>\r\n\r\n <mat-dialog-content class=\"mat-typography dialog-scroll-content\">\r\n\r\n <spa-steps *ngIf=\"stepConfig && details && !stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && !statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && !formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n\r\n <div class=\"tin-input\" style=\"font-size:14px\">\r\n\r\n <p *ngIf=\"formConfig && !details\"><em>Loading...</em></p>\r\n\r\n <spa-form *ngIf=\"formConfig && details\" [files]=\"files\" [data]=\"details\" [config]=\"formConfig\" (inputChange)=\"inputChanged($event)\">\r\n <ng-template #dynamicSelect let-field=\"field\" let-data=\"data\" let-testReadOnly=\"testReadOnly\" let-testRequired=\"testRequired\" let-selectChanged=\"selectChanged\">\r\n <spa-select-internal\r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [width]=\"field.width\"\r\n [nullable]=\"field.nullable\"\r\n [options]=\"field.options\"\r\n [masterOptions]=\"field.masterOptions\"\r\n [masterField]=\"field.masterField\"\r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\"\r\n [optionValue]=\"field.optionValue ?? 'value'\"\r\n [(value)]=\"data[field.name]\"\r\n [defaultFirstValue]=\"field.defaultFirstValue\"\r\n [required]=\"testRequired(field)\"\r\n [readonly]=\"testReadOnly(field)\"\r\n [hint]=\"field.hint\"\r\n [detailsConfig]=\"field.detailsConfig\"\r\n [loadAction]=\"field.loadAction\"\r\n [loadIDField]=\"field.loadIDField\"\r\n [field]=\"field\"\r\n [data]=\"data\"\r\n [infoMessage]=\"field.infoMessage\"\r\n [copyContent]=\"field.copyContent\"\r\n (valueChange)=\"selectChanged(field)\"\r\n ></spa-select-internal>\r\n </ng-template>\r\n </spa-form>\r\n\r\n <!-- Changed: Replace mat-tab-group with spa-tabs-lite component -->\r\n <spa-tabs-lite \r\n *ngIf=\"tableConfigs\"\r\n [tableConfigs]=\"tableConfigs\"\r\n [reload]=\"tableReload\"\r\n [parentDetails]=\"details\"\r\n (formRefresh)=\"loadData(formConfig.loadAction, false)\">\r\n </spa-tabs-lite>\r\n\r\n </div>\r\n\r\n </mat-dialog-content>\r\n\r\n\r\n </div>\r\n\r\n <mat-dialog-actions>\r\n\r\n <div>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='create' && createButton\" color=\"primary\" \r\n (click)=\"create()\" cdkFocusInitial>{{createButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='edit' && editButton\" color=\"primary\" \r\n (click)=\"edit()\" cdkFocusInitial>{{editButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <ng-container *ngFor=\"let btn of extraButtons\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && testVisible(details,btn.name)\" mat-stroked-button [disabled]=\"isProcessing || testDisabled(details,btn.name)\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\" (click)=\"custom(btn)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"btn.icon\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\">{{btn.icon.name}}</mat-icon>\r\n {{btn.display ?? btn.name | titlecase}}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Changed: Bottom close button now uses conditional display and custom text -->\r\n <button *ngIf=\"shouldShowBottomClose()\" mat-stroked-button color=\"primary\" (click)=\"close()\">{{getCloseText()}}</button>\r\n\r\n </div>\r\n\r\n <div class=\"col d-flex justify-content-end\" *ngIf=\"smallScreen\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Delete\" [disabled]=\"isProcessing\" style=\"color: red;\" (click)=\"delete()\" *ngIf=\"formConfig.mode!='create' && deleteButton\"><mat-icon>delete</mat-icon></button>\r\n </div>\r\n\r\n\r\n </mat-dialog-actions>\r\n\r\n\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: i14$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: CheckComponent, selector: "spa-check", inputs: ["readonly", "display", "value", "infoMessage"], outputs: ["valueChange", "click", "check", "uncheck", "infoClick"] }, { kind: "component", type: StepsComponent, selector: "spa-steps", inputs: ["value", "config", "data"] }, { kind: "component", type: FormComponent, selector: "spa-form", inputs: ["files", "data", "config"], outputs: ["buttonClick", "inputChange"] }, { kind: "component", type: AlertComponent, selector: "spa-alert", inputs: ["config", "data"] }, { kind: "component", type: SelectInternalComponent, selector: "spa-select-internal", inputs: ["detailsConfig"] }, { kind: "component", type: TabsLiteComponent, selector: "spa-tabs-lite", inputs: ["tableConfigs", "reload", "parentDetails"], outputs: ["formRefresh"] }, { kind: "component", type: StatusesComponent, selector: "spa-statuses", inputs: ["config", "data"] }, { kind: "pipe", type: i2.TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
12910
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: DetailsDialogInternal, isStandalone: false, selector: "spa-detailsDialog-internal", outputs: { inputChange: "inputChange" }, ngImport: i0, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <spa-check *ngIf=\"detailsConfig.autoRefreshConfig\" display=\"Auto Refresh\" [(value)]=\"autoRefreshEnabled\" (valueChange)=\"toggleAutoRefresh()\"></spa-check>\r\n <!-- Added: Keep Open checkbox \u2014 visible when developer enables allowUserKeepOpen -->\r\n <spa-check *ngIf=\"detailsConfig.allowUserKeepOpen\" display=\"Keep Open\" [(value)]=\"userKeepOpen\"></spa-check>\r\n\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && testVisible(details,editButton.name)\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"testDisabled(details,editButton.name)\"><mat-icon>edit</mat-icon></button>\r\n </div>\r\n\r\n <button [disabled]=\"isProcessing\" *ngIf=\"loadByAction\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Refresh\" color=\"primary\" (click)=\"loadData(formConfig.loadAction, detailsConfig.causeTableRefresh)\"><mat-icon class=\"refreshIcon\">cached</mat-icon></button>\r\n \r\n <!-- Added: Top close button when position is 'top' -->\r\n <button *ngIf=\"shouldShowTopClose()\" [disabled]=\"isProcessing\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Close\" (click)=\"close()\"><mat-icon>close</mat-icon></button>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div style=\"padding-left: 24px; padding-right: 24px;\">\r\n <spa-steps *ngIf=\"stepConfig && details && stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n </div>\r\n\r\n <mat-dialog-content class=\"mat-typography dialog-scroll-content\">\r\n\r\n <spa-steps *ngIf=\"stepConfig && details && !stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && !statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && !formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n\r\n <div class=\"tin-input\" style=\"font-size:14px\">\r\n\r\n <p *ngIf=\"formConfig && !details\"><em>Loading...</em></p>\r\n\r\n <spa-form *ngIf=\"formConfig && details\" [files]=\"files\" [data]=\"details\" [config]=\"formConfig\" (inputChange)=\"inputChanged($event)\">\r\n <ng-template #dynamicSelect let-field=\"field\" let-data=\"data\" let-testReadOnly=\"testReadOnly\" let-testRequired=\"testRequired\" let-selectChanged=\"selectChanged\">\r\n <spa-select-internal\r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [width]=\"field.width\"\r\n [nullable]=\"field.nullable\"\r\n [options]=\"field.options\"\r\n [masterOptions]=\"field.masterOptions\"\r\n [masterField]=\"field.masterField\"\r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\"\r\n [optionValue]=\"field.optionValue ?? 'value'\"\r\n [(value)]=\"data[field.name]\"\r\n [defaultFirstValue]=\"field.defaultFirstValue\"\r\n [required]=\"testRequired(field)\"\r\n [readonly]=\"testReadOnly(field)\"\r\n [hint]=\"field.hint\"\r\n [detailsConfig]=\"field.detailsConfig\"\r\n [loadAction]=\"field.loadAction\"\r\n [loadIDField]=\"field.loadIDField\"\r\n [field]=\"field\"\r\n [data]=\"data\"\r\n [infoMessage]=\"field.infoMessage\"\r\n [copyContent]=\"field.copyContent\"\r\n (valueChange)=\"selectChanged(field)\"\r\n ></spa-select-internal>\r\n </ng-template>\r\n </spa-form>\r\n\r\n <!-- Changed: Replace mat-tab-group with spa-tabs-lite component -->\r\n <spa-tabs-lite \r\n *ngIf=\"tableConfigs\"\r\n [tableConfigs]=\"tableConfigs\"\r\n [reload]=\"tableReload\"\r\n [parentDetails]=\"details\"\r\n (formRefresh)=\"loadData(formConfig.loadAction, false)\">\r\n </spa-tabs-lite>\r\n\r\n </div>\r\n\r\n </mat-dialog-content>\r\n\r\n\r\n </div>\r\n\r\n <mat-dialog-actions>\r\n\r\n <div>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='create' && createButton\" color=\"primary\" \r\n (click)=\"create()\" cdkFocusInitial>{{createButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='edit' && editButton\" color=\"primary\" \r\n (click)=\"edit()\" cdkFocusInitial>{{editButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <ng-container *ngFor=\"let btn of extraButtons\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && testVisible(details,btn.name)\" mat-stroked-button [disabled]=\"isProcessing || testDisabled(details,btn.name)\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\" (click)=\"custom(btn)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"btn.icon\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\">{{btn.icon.name}}</mat-icon>\r\n {{btn.display ?? btn.name | titlecase}}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Changed: Bottom close button now uses conditional display and custom text -->\r\n <button *ngIf=\"shouldShowBottomClose()\" mat-stroked-button color=\"primary\" (click)=\"close()\">{{getCloseText()}}</button>\r\n\r\n </div>\r\n\r\n <div class=\"col d-flex justify-content-end\" *ngIf=\"smallScreen\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Delete\" [disabled]=\"isProcessing\" style=\"color: red;\" (click)=\"delete()\" *ngIf=\"formConfig.mode!='create' && deleteButton\"><mat-icon>delete</mat-icon></button>\r\n </div>\r\n\r\n\r\n </mat-dialog-actions>\r\n\r\n\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: i14$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: CheckComponent, selector: "spa-check", inputs: ["readonly", "display", "value", "infoMessage"], outputs: ["valueChange", "click", "check", "uncheck", "infoClick"] }, { kind: "component", type: StepsComponent, selector: "spa-steps", inputs: ["value", "config", "data"] }, { kind: "component", type: FormComponent, selector: "spa-form", inputs: ["files", "data", "config"], outputs: ["buttonClick", "inputChange"] }, { kind: "component", type: AlertComponent, selector: "spa-alert", inputs: ["config", "data"] }, { kind: "component", type: SelectInternalComponent, selector: "spa-select-internal", inputs: ["detailsConfig"] }, { kind: "component", type: TabsLiteComponent, selector: "spa-tabs-lite", inputs: ["tableConfigs", "reload", "parentDetails"], outputs: ["formRefresh"] }, { kind: "component", type: StatusesComponent, selector: "spa-statuses", inputs: ["config", "data"] }, { kind: "pipe", type: i2.TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
12732
12911
  }
12733
12912
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DetailsDialogInternal, decorators: [{
12734
12913
  type: Component,
12735
- args: [{ selector: 'spa-detailsDialog-internal', standalone: false, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <spa-check *ngIf=\"detailsConfig.autoRefreshConfig\" display=\"Auto Refresh\" [(value)]=\"autoRefreshEnabled\" (valueChange)=\"toggleAutoRefresh()\"></spa-check>\r\n\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && testVisible(details,editButton.name)\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"testDisabled(details,editButton.name)\"><mat-icon>edit</mat-icon></button>\r\n </div>\r\n\r\n <button [disabled]=\"isProcessing\" *ngIf=\"loadByAction\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Refresh\" color=\"primary\" (click)=\"loadData(formConfig.loadAction, detailsConfig.causeTableRefresh)\"><mat-icon class=\"refreshIcon\">cached</mat-icon></button>\r\n \r\n <!-- Added: Top close button when position is 'top' -->\r\n <button *ngIf=\"shouldShowTopClose()\" [disabled]=\"isProcessing\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Close\" (click)=\"close()\"><mat-icon>close</mat-icon></button>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div style=\"padding-left: 24px; padding-right: 24px;\">\r\n <spa-steps *ngIf=\"stepConfig && details && stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n </div>\r\n\r\n <mat-dialog-content class=\"mat-typography dialog-scroll-content\">\r\n\r\n <spa-steps *ngIf=\"stepConfig && details && !stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && !statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && !formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n\r\n <div class=\"tin-input\" style=\"font-size:14px\">\r\n\r\n <p *ngIf=\"formConfig && !details\"><em>Loading...</em></p>\r\n\r\n <spa-form *ngIf=\"formConfig && details\" [files]=\"files\" [data]=\"details\" [config]=\"formConfig\" (inputChange)=\"inputChanged($event)\">\r\n <ng-template #dynamicSelect let-field=\"field\" let-data=\"data\" let-testReadOnly=\"testReadOnly\" let-testRequired=\"testRequired\" let-selectChanged=\"selectChanged\">\r\n <spa-select-internal\r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [width]=\"field.width\"\r\n [nullable]=\"field.nullable\"\r\n [options]=\"field.options\"\r\n [masterOptions]=\"field.masterOptions\"\r\n [masterField]=\"field.masterField\"\r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\"\r\n [optionValue]=\"field.optionValue ?? 'value'\"\r\n [(value)]=\"data[field.name]\"\r\n [defaultFirstValue]=\"field.defaultFirstValue\"\r\n [required]=\"testRequired(field)\"\r\n [readonly]=\"testReadOnly(field)\"\r\n [hint]=\"field.hint\"\r\n [detailsConfig]=\"field.detailsConfig\"\r\n [loadAction]=\"field.loadAction\"\r\n [loadIDField]=\"field.loadIDField\"\r\n [field]=\"field\"\r\n [data]=\"data\"\r\n [infoMessage]=\"field.infoMessage\"\r\n [copyContent]=\"field.copyContent\"\r\n (valueChange)=\"selectChanged(field)\"\r\n ></spa-select-internal>\r\n </ng-template>\r\n </spa-form>\r\n\r\n <!-- Changed: Replace mat-tab-group with spa-tabs-lite component -->\r\n <spa-tabs-lite \r\n *ngIf=\"tableConfigs\"\r\n [tableConfigs]=\"tableConfigs\"\r\n [reload]=\"tableReload\"\r\n [parentDetails]=\"details\"\r\n (formRefresh)=\"loadData(formConfig.loadAction, false)\">\r\n </spa-tabs-lite>\r\n\r\n </div>\r\n\r\n </mat-dialog-content>\r\n\r\n\r\n </div>\r\n\r\n <mat-dialog-actions>\r\n\r\n <div>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='create' && createButton\" color=\"primary\" \r\n (click)=\"create()\" cdkFocusInitial>{{createButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='edit' && editButton\" color=\"primary\" \r\n (click)=\"edit()\" cdkFocusInitial>{{editButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <ng-container *ngFor=\"let btn of extraButtons\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && testVisible(details,btn.name)\" mat-stroked-button [disabled]=\"isProcessing || testDisabled(details,btn.name)\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\" (click)=\"custom(btn)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"btn.icon\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\">{{btn.icon.name}}</mat-icon>\r\n {{btn.display ?? btn.name | titlecase}}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Changed: Bottom close button now uses conditional display and custom text -->\r\n <button *ngIf=\"shouldShowBottomClose()\" mat-stroked-button color=\"primary\" (click)=\"close()\">{{getCloseText()}}</button>\r\n\r\n </div>\r\n\r\n <div class=\"col d-flex justify-content-end\" *ngIf=\"smallScreen\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Delete\" [disabled]=\"isProcessing\" style=\"color: red;\" (click)=\"delete()\" *ngIf=\"formConfig.mode!='create' && deleteButton\"><mat-icon>delete</mat-icon></button>\r\n </div>\r\n\r\n\r\n </mat-dialog-actions>\r\n\r\n\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"] }]
12914
+ args: [{ selector: 'spa-detailsDialog-internal', standalone: false, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <spa-check *ngIf=\"detailsConfig.autoRefreshConfig\" display=\"Auto Refresh\" [(value)]=\"autoRefreshEnabled\" (valueChange)=\"toggleAutoRefresh()\"></spa-check>\r\n <!-- Added: Keep Open checkbox \u2014 visible when developer enables allowUserKeepOpen -->\r\n <spa-check *ngIf=\"detailsConfig.allowUserKeepOpen\" display=\"Keep Open\" [(value)]=\"userKeepOpen\"></spa-check>\r\n\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && testVisible(details,editButton.name)\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"testDisabled(details,editButton.name)\"><mat-icon>edit</mat-icon></button>\r\n </div>\r\n\r\n <button [disabled]=\"isProcessing\" *ngIf=\"loadByAction\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Refresh\" color=\"primary\" (click)=\"loadData(formConfig.loadAction, detailsConfig.causeTableRefresh)\"><mat-icon class=\"refreshIcon\">cached</mat-icon></button>\r\n \r\n <!-- Added: Top close button when position is 'top' -->\r\n <button *ngIf=\"shouldShowTopClose()\" [disabled]=\"isProcessing\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Close\" (click)=\"close()\"><mat-icon>close</mat-icon></button>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div style=\"padding-left: 24px; padding-right: 24px;\">\r\n <spa-steps *ngIf=\"stepConfig && details && stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n </div>\r\n\r\n <mat-dialog-content class=\"mat-typography dialog-scroll-content\">\r\n\r\n <spa-steps *ngIf=\"stepConfig && details && !stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && !statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && !formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n\r\n <div class=\"tin-input\" style=\"font-size:14px\">\r\n\r\n <p *ngIf=\"formConfig && !details\"><em>Loading...</em></p>\r\n\r\n <spa-form *ngIf=\"formConfig && details\" [files]=\"files\" [data]=\"details\" [config]=\"formConfig\" (inputChange)=\"inputChanged($event)\">\r\n <ng-template #dynamicSelect let-field=\"field\" let-data=\"data\" let-testReadOnly=\"testReadOnly\" let-testRequired=\"testRequired\" let-selectChanged=\"selectChanged\">\r\n <spa-select-internal\r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [width]=\"field.width\"\r\n [nullable]=\"field.nullable\"\r\n [options]=\"field.options\"\r\n [masterOptions]=\"field.masterOptions\"\r\n [masterField]=\"field.masterField\"\r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\"\r\n [optionValue]=\"field.optionValue ?? 'value'\"\r\n [(value)]=\"data[field.name]\"\r\n [defaultFirstValue]=\"field.defaultFirstValue\"\r\n [required]=\"testRequired(field)\"\r\n [readonly]=\"testReadOnly(field)\"\r\n [hint]=\"field.hint\"\r\n [detailsConfig]=\"field.detailsConfig\"\r\n [loadAction]=\"field.loadAction\"\r\n [loadIDField]=\"field.loadIDField\"\r\n [field]=\"field\"\r\n [data]=\"data\"\r\n [infoMessage]=\"field.infoMessage\"\r\n [copyContent]=\"field.copyContent\"\r\n (valueChange)=\"selectChanged(field)\"\r\n ></spa-select-internal>\r\n </ng-template>\r\n </spa-form>\r\n\r\n <!-- Changed: Replace mat-tab-group with spa-tabs-lite component -->\r\n <spa-tabs-lite \r\n *ngIf=\"tableConfigs\"\r\n [tableConfigs]=\"tableConfigs\"\r\n [reload]=\"tableReload\"\r\n [parentDetails]=\"details\"\r\n (formRefresh)=\"loadData(formConfig.loadAction, false)\">\r\n </spa-tabs-lite>\r\n\r\n </div>\r\n\r\n </mat-dialog-content>\r\n\r\n\r\n </div>\r\n\r\n <mat-dialog-actions>\r\n\r\n <div>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='create' && createButton\" color=\"primary\" \r\n (click)=\"create()\" cdkFocusInitial>{{createButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='edit' && editButton\" color=\"primary\" \r\n (click)=\"edit()\" cdkFocusInitial>{{editButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <ng-container *ngFor=\"let btn of extraButtons\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && testVisible(details,btn.name)\" mat-stroked-button [disabled]=\"isProcessing || testDisabled(details,btn.name)\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\" (click)=\"custom(btn)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"btn.icon\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\">{{btn.icon.name}}</mat-icon>\r\n {{btn.display ?? btn.name | titlecase}}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Changed: Bottom close button now uses conditional display and custom text -->\r\n <button *ngIf=\"shouldShowBottomClose()\" mat-stroked-button color=\"primary\" (click)=\"close()\">{{getCloseText()}}</button>\r\n\r\n </div>\r\n\r\n <div class=\"col d-flex justify-content-end\" *ngIf=\"smallScreen\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Delete\" [disabled]=\"isProcessing\" style=\"color: red;\" (click)=\"delete()\" *ngIf=\"formConfig.mode!='create' && deleteButton\"><mat-icon>delete</mat-icon></button>\r\n </div>\r\n\r\n\r\n </mat-dialog-actions>\r\n\r\n\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"] }]
12736
12915
  }], ctorParameters: () => [{ type: i1$3.BreakpointObserver }, { type: LoaderService }, { type: DataServiceLib }, { type: MessageService }, { type: i1.MatDialogRef }, { type: DetailsDialogConfig, decorators: [{
12737
12916
  type: Inject,
12738
12917
  args: [MAT_DIALOG_DATA]
@@ -12742,7 +12921,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
12742
12921
  ;
12743
12922
 
12744
12923
  class TableInternalComponent {
12745
- constructor(dataService, messageService, breakpointObserver, dialog, buttonService, dialogService, tableConfigService, conditionService, authService) {
12924
+ constructor(dataService, messageService, breakpointObserver, dialog, buttonService, dialogService, tableConfigService, conditionService, authService, signalRService) {
12746
12925
  this.dataService = dataService;
12747
12926
  this.messageService = messageService;
12748
12927
  this.breakpointObserver = breakpointObserver;
@@ -12752,6 +12931,7 @@ class TableInternalComponent {
12752
12931
  this.tableConfigService = tableConfigService;
12753
12932
  this.conditionService = conditionService;
12754
12933
  this.authService = authService;
12934
+ this.signalRService = signalRService;
12755
12935
  this.elevation = "mat-elevation-z5";
12756
12936
  this.actionsWidth = "50px";
12757
12937
  this.showFilterButton = true;
@@ -12773,6 +12953,10 @@ class TableInternalComponent {
12773
12953
  this.actionClick = new EventEmitter();
12774
12954
  this.inputChange = new EventEmitter();
12775
12955
  this.actionResponse = new EventEmitter();
12956
+ // Changed: Real-time SignalR subscription management
12957
+ this.realTimeSubs = [];
12958
+ this.realTimeEntityName = '';
12959
+ this.pendingRealTimeUpdate = false;
12776
12960
  // detect screen size changes
12777
12961
  this.breakpointObserver.observe(["(max-width: 600px)"]).subscribe((result) => {
12778
12962
  this.smallScreen = result.matches;
@@ -12812,6 +12996,8 @@ class TableInternalComponent {
12812
12996
  this.dataSource = this.data;
12813
12997
  this.tableDataSource = new MatTableDataSource(this.dataSource);
12814
12998
  this.setPaginator(); // Set paginator after datasource initialization
12999
+ if (this.config.realTime)
13000
+ this.setupRealTimeSubscriptions(); // Changed: Subscribe to SignalR entity changes
12815
13001
  }
12816
13002
  updateTableConfiguration() {
12817
13003
  this.elevation = this.getElevationClass(this.config?.elevation);
@@ -13038,12 +13224,16 @@ class TableInternalComponent {
13038
13224
  const buttonName = button.name;
13039
13225
  let btn = {
13040
13226
  ...button,
13041
- detailsConfig: button.detailsConfig ? button.detailsConfig : {
13042
- formConfig: this.config.formConfig,
13043
- stepConfig: this.config.stepConfig,
13044
- buttons: this.config.buttons,
13045
- heroField: this.config.heroField,
13046
- heroValue: this.config.heroValue,
13227
+ detailsConfig: {
13228
+ ...(button.detailsConfig ?? {
13229
+ formConfig: this.config.formConfig,
13230
+ stepConfig: this.config.stepConfig,
13231
+ buttons: this.config.buttons,
13232
+ heroField: this.config.heroField,
13233
+ heroValue: this.config.heroValue,
13234
+ }),
13235
+ allowUserKeepOpen: this.config.allowUserKeepOpen, // Changed: table config is the single source of truth for this setting
13236
+ keepOpenBehavior: this.config.keepOpenBehavior, // Changed: propagate create behavior preference from table config
13047
13237
  }
13048
13238
  };
13049
13239
  this.dialogService.openConfiguredDetailsDialog(btn, row, DetailsDialogInternal).subscribe(result => {
@@ -13057,7 +13247,7 @@ class TableInternalComponent {
13057
13247
  else if (result.message === 'success') {
13058
13248
  this.actionClickedEmit(buttonName, result.data);
13059
13249
  this.actionResponse.emit({ name: buttonName, data: result.data });
13060
- this.refreshClicked();
13250
+ this.realTimeRefreshOrFallback(); // Changed: Use real-time update if enabled, otherwise refresh
13061
13251
  this.actionSuccess.emit();
13062
13252
  if (buttonName == 'create' && btn.editOnSuccess) {
13063
13253
  this.editModel(result.data.data);
@@ -13123,7 +13313,7 @@ class TableInternalComponent {
13123
13313
  else {
13124
13314
  this.messageService.toast("Updated");
13125
13315
  }
13126
- this.refreshClicked();
13316
+ this.realTimeRefreshOrFallback(); // Changed: Use real-time update if enabled, otherwise refresh
13127
13317
  this.actionSuccess.emit();
13128
13318
  }
13129
13319
  else {
@@ -13152,13 +13342,87 @@ class TableInternalComponent {
13152
13342
  return "mat-elevation-z5";
13153
13343
  }
13154
13344
  }
13155
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TableInternalComponent, deps: [{ token: DataServiceLib }, { token: MessageService }, { token: i1$3.BreakpointObserver }, { token: i1.MatDialog }, { token: ButtonService }, { token: DialogService }, { token: TableConfigService }, { token: ConditionService }, { token: AuthService }], target: i0.ɵɵFactoryTarget.Component }); }
13156
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TableInternalComponent, isStandalone: false, selector: "spa-table-internal", inputs: { data: "data", tileData: "tileData", config: "config", reload: "reload", activeTab: "activeTab", inTab: "inTab" }, outputs: { dataLoad: "dataLoad", actionSuccess: "actionSuccess", refreshClick: "refreshClick", searchClick: "searchClick", createClick: "createClick", actionClick: "actionClick", inputChange: "inputChange", actionResponse: "actionResponse" }, viewQueries: [{ propertyName: "tablePaginator", first: true, predicate: ["tablePaginator"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "\r\n<ng-container *ngIf=\"hasFormAccess\">\r\n\r\n <!-- Search -->\r\n <spa-search\r\n *ngIf=\"config.searchConfig\" [config]=\"config.searchConfig\" [smallScreen]=\"smallScreen\" [tableDataSource]=\"tableDataSource\" style=\"margin-bottom: 20px;\" (searchClick)=\"searchClicked($event)\">\r\n </spa-search>\r\n\r\n <!-- Header -->\r\n <app-table-header\r\n [config]=\"config\" [data]=\"dataSource\" [tableDataSource]=\"tableDataSource\" [tileConfig]=\"config.tileConfig\" [tileData]=\"tileData\" [tileReload]=\"tileReload\" [lastSearch]=\"lastSearch\" [smallScreen]=\"smallScreen\"\r\n [showFilterButton]=\"showFilterButton\" (createClick)=\"newModel()\" (customClick)=\"customModel($event,null)\"\r\n (refreshClick)=\"refreshClicked()\" (tileClick)=\"tileClicked($event)\" (tileUnClick)=\"tileUnClicked($event)\">\r\n </app-table-header>\r\n\r\n\r\n <!-- Table -->\r\n <div *ngIf=\"!config.viewType || config?.viewType === 'table'\">\r\n\r\n <p *ngIf=\"!config\"><em>Configure Table</em></p>\r\n <p *ngIf=\"!dataSource\"><em>Loading...</em></p>\r\n\r\n <div *ngIf=\"dataSource && (!smallScreen || (smallScreen && dataSource?.length > 0))\">\r\n\r\n <table mat-table [dataSource]=\"tableDataSource\" [ngClass]=\"elevation\">\r\n\r\n <ng-container *ngFor=\"let column of config.columns\" [matColumnDef]=\"column.name\">\r\n <th mat-header-cell *matHeaderCellDef >{{ column.alias ?? column.name | camelToWords }}</th>\r\n <td mat-cell *matCellDef=\"let row;\" class=\"right-padding\" >\r\n\r\n <!-- Rows -->\r\n <app-table-row [column]=\"column\" [row]=\"row\" [config]=\"config\" [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked(column.name, row)\" (columnClick)=\"columnClicked(column, row)\" (showBannerEvent)=\"showBanner($event)\">\r\n </app-table-row>\r\n\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"action\">\r\n <th mat-header-cell *matHeaderCellDef> Action </th>\r\n <td mat-cell *matCellDef=\"let row\" [ngStyle]=\"{width:false ? '50px' : actionsWidth}\">\r\n <div class=\"action-buttons-container\">\r\n\r\n <!-- Actions -->\r\n <app-table-action\r\n [displayedButtons]=\"displayedButtons\" [config]=\"config\" [smallScreen]=\"smallScreen\" [row]=\"row\" (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </app-table-action>\r\n\r\n </div>\r\n </td>\r\n </ng-container>\r\n\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" [ngClass]=\"{'make-gray': (config.greyOut && config.greyOut(row)) || row.pendingApproval}\"></tr>\r\n </table>\r\n\r\n </div>\r\n\r\n <!-- Changed: Removed *ngIf condition to keep paginator always in DOM and maintain ViewChild reference -->\r\n <!-- Changed: Added CSS class binding to hide when no data instead of conditional rendering -->\r\n <mat-paginator \r\n #tablePaginator \r\n [pageSizeOptions]=\"config.pageSizes ?? [10, 20, 50]\" \r\n [ngClass]=\"{'paginator-hidden': !dataSource || (smallScreen && dataSource?.length === 0)}\"\r\n showFirstLastButtons>\r\n </mat-paginator>\r\n\r\n </div>\r\n \r\n <!-- Capsules -->\r\n <spa-capsules *ngIf=\"config?.viewType === 'capsule'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </spa-capsules>\r\n\r\n <!-- Cards -->\r\n <spa-cards *ngIf=\"config?.viewType === 'card'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\"\r\n (columnClick)=\"columnClicked($event.column, $event.row)\"\r\n (showBannerEvent)=\"showBanner($event)\">\r\n </spa-cards>\r\n\r\n <!-- Groups -->\r\n <spa-groups *ngIf=\"config?.viewType === 'grouped'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row, $event.group, $event.button)\">\r\n </spa-groups>\r\n\r\n <div class=\"tin-center\">\r\n <p *ngIf=\"dataSource?.length == 0\"><em>{{config.noDataMessage ?? 'No Data'}}</em></p>\r\n </div>\r\n\r\n</ng-container>\r\n\r\n\r\n<ng-container *ngIf=\"!hasFormAccess\">\r\n <div class=\"tin-center\">\r\n <p><em>Access Restricted</em></p>\r\n </div>\r\n</ng-container>\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i11.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i11.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i11.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i11.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i11.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i11.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i11.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i11.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i11.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i11.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i12$2.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: SearchComponent, selector: "spa-search", inputs: ["config", "smallScreen", "tableDataSource"], outputs: ["searchClick"] }, { kind: "component", type: TableHeaderComponent, selector: "app-table-header", inputs: ["lastSearch", "config", "hideTitle", "tableDataSource", "tileConfig", "smallScreen", "tileReload", "showFilterButton", "data", "tileData"], outputs: ["createClick", "customClick", "refreshClick", "tileClick", "tileUnClick"] }, { kind: "component", type: TableRowComponent, selector: "app-table-row", inputs: ["column", "row", "config", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: TableActionComponent, selector: "app-table-action", inputs: ["displayedButtons", "config", "row", "smallScreen"], outputs: ["actionClick"] }, { kind: "component", type: CapsulesComponent, selector: "spa-capsules", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "component", type: CardsComponent, selector: "spa-cards", inputs: ["config", "dataSource", "displayedButtons", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: GroupsComponent, selector: "spa-groups", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
13345
+ // Changed: Skip refresh if real-time is enabled SignalR will handle it. 3s safety fallback.
13346
+ realTimeRefreshOrFallback() {
13347
+ if (!this.config.realTime) {
13348
+ this.refreshClicked();
13349
+ return;
13350
+ }
13351
+ this.pendingRealTimeUpdate = false;
13352
+ setTimeout(() => {
13353
+ if (!this.pendingRealTimeUpdate)
13354
+ this.refreshClicked(); // Fallback if SignalR event didn't arrive
13355
+ }, 3000);
13356
+ }
13357
+ // Changed: Set up SignalR subscriptions for real-time entity change updates
13358
+ setupRealTimeSubscriptions() {
13359
+ this.realTimeEntityName = this.config.entityName || this.deriveEntityName();
13360
+ // Changed: Subscribe to entity created — add new row to table
13361
+ this.realTimeSubs.push(this.signalRService.entityCreated$.subscribe(event => {
13362
+ if (event.entityName !== this.realTimeEntityName)
13363
+ return;
13364
+ if (!this.tableDataSource)
13365
+ return;
13366
+ this.pendingRealTimeUpdate = true;
13367
+ const data = [...this.tableDataSource.data, event.data];
13368
+ this.tableDataSource.data = data;
13369
+ this.dataSource = data;
13370
+ }));
13371
+ // Changed: Subscribe to entity updated — replace existing row by ID
13372
+ this.realTimeSubs.push(this.signalRService.entityUpdated$.subscribe(event => {
13373
+ if (event.entityName !== this.realTimeEntityName)
13374
+ return;
13375
+ if (!this.tableDataSource)
13376
+ return;
13377
+ this.pendingRealTimeUpdate = true;
13378
+ const idKey = this.findIdKey(event.data);
13379
+ if (!idKey)
13380
+ return;
13381
+ const data = this.tableDataSource.data.map(row => row[idKey] === event.data[idKey] ? event.data : row);
13382
+ this.tableDataSource.data = data;
13383
+ this.dataSource = data;
13384
+ }));
13385
+ // Changed: Subscribe to entity deleted — remove row by ID
13386
+ this.realTimeSubs.push(this.signalRService.entityDeleted$.subscribe(event => {
13387
+ if (event.entityName !== this.realTimeEntityName)
13388
+ return;
13389
+ if (!this.tableDataSource)
13390
+ return;
13391
+ this.pendingRealTimeUpdate = true;
13392
+ const idKey = this.findIdKey(this.tableDataSource.data[0]);
13393
+ if (!idKey)
13394
+ return;
13395
+ const data = this.tableDataSource.data.filter(row => row[idKey] !== event.data);
13396
+ this.tableDataSource.data = data;
13397
+ this.dataSource = data;
13398
+ }));
13399
+ }
13400
+ // Changed: Derive entity name from loadAction URL (e.g. 'products/all/x' → 'Product')
13401
+ deriveEntityName() {
13402
+ const url = this.config.loadAction?.url || '';
13403
+ const segment = url.split('/')[0];
13404
+ if (!segment)
13405
+ return '';
13406
+ const name = segment.charAt(0).toUpperCase() + segment.slice(1);
13407
+ return name.endsWith('s') ? name.slice(0, -1) : name;
13408
+ }
13409
+ // Changed: Find the ID property key in a row object (convention: ends with 'ID')
13410
+ findIdKey(row) {
13411
+ if (!row)
13412
+ return null;
13413
+ return Object.keys(row).find(k => k.endsWith('ID') && k !== 'TenantID') || null;
13414
+ }
13415
+ // Changed: Clean up SignalR subscriptions on component destroy
13416
+ ngOnDestroy() {
13417
+ this.realTimeSubs.forEach(s => s.unsubscribe());
13418
+ }
13419
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TableInternalComponent, deps: [{ token: DataServiceLib }, { token: MessageService }, { token: i1$3.BreakpointObserver }, { token: i1.MatDialog }, { token: ButtonService }, { token: DialogService }, { token: TableConfigService }, { token: ConditionService }, { token: AuthService }, { token: SignalRService }], target: i0.ɵɵFactoryTarget.Component }); }
13420
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TableInternalComponent, isStandalone: false, selector: "spa-table-internal", inputs: { data: "data", tileData: "tileData", config: "config", reload: "reload", activeTab: "activeTab", inTab: "inTab" }, outputs: { dataLoad: "dataLoad", actionSuccess: "actionSuccess", refreshClick: "refreshClick", searchClick: "searchClick", createClick: "createClick", actionClick: "actionClick", inputChange: "inputChange", actionResponse: "actionResponse" }, viewQueries: [{ propertyName: "tablePaginator", first: true, predicate: ["tablePaginator"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "\r\n<ng-container *ngIf=\"hasFormAccess\">\r\n\r\n <!-- Search -->\r\n <spa-search\r\n *ngIf=\"config.searchConfig\" [config]=\"config.searchConfig\" [smallScreen]=\"smallScreen\" [tableDataSource]=\"tableDataSource\" style=\"margin-bottom: 20px;\" (searchClick)=\"searchClicked($event)\">\r\n </spa-search>\r\n\r\n <!-- Header -->\r\n <app-table-header\r\n [config]=\"config\" [data]=\"dataSource\" [tableDataSource]=\"tableDataSource\" [tileConfig]=\"config.tileConfig\" [tileData]=\"tileData\" [tileReload]=\"tileReload\" [lastSearch]=\"lastSearch\" [smallScreen]=\"smallScreen\"\r\n [showFilterButton]=\"showFilterButton\" (createClick)=\"newModel()\" (customClick)=\"customModel($event,null)\"\r\n (refreshClick)=\"refreshClicked()\" (tileClick)=\"tileClicked($event)\" (tileUnClick)=\"tileUnClicked($event)\">\r\n </app-table-header>\r\n\r\n\r\n <!-- Table -->\r\n <div *ngIf=\"!config.viewType || config?.viewType === 'table'\">\r\n\r\n <p *ngIf=\"!config\"><em>Configure Table</em></p>\r\n <p *ngIf=\"!dataSource\"><em>Loading...</em></p>\r\n\r\n <div *ngIf=\"dataSource && (!smallScreen || (smallScreen && dataSource?.length > 0))\">\r\n\r\n <table mat-table [dataSource]=\"tableDataSource\" [ngClass]=\"elevation\">\r\n\r\n <ng-container *ngFor=\"let column of config.columns\" [matColumnDef]=\"column.name\">\r\n <th mat-header-cell *matHeaderCellDef >{{ column.alias ?? column.name | camelToWords }}</th>\r\n <td mat-cell *matCellDef=\"let row;\" class=\"right-padding\" >\r\n\r\n <!-- Rows -->\r\n <app-table-row [column]=\"column\" [row]=\"row\" [config]=\"config\" [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked(column.name, row)\" (columnClick)=\"columnClicked(column, row)\" (showBannerEvent)=\"showBanner($event)\">\r\n </app-table-row>\r\n\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"action\">\r\n <th mat-header-cell *matHeaderCellDef> Action </th>\r\n <td mat-cell *matCellDef=\"let row\" [ngStyle]=\"{width:false ? '50px' : actionsWidth}\">\r\n <div class=\"action-buttons-container\">\r\n\r\n <!-- Actions -->\r\n <app-table-action\r\n [displayedButtons]=\"displayedButtons\" [config]=\"config\" [smallScreen]=\"smallScreen\" [row]=\"row\" (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </app-table-action>\r\n\r\n </div>\r\n </td>\r\n </ng-container>\r\n\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" [ngClass]=\"{'make-gray': (config.greyOut && config.greyOut(row)) || row.pendingApproval}\"></tr>\r\n </table>\r\n\r\n </div>\r\n\r\n <!-- Changed: Removed *ngIf condition to keep paginator always in DOM and maintain ViewChild reference -->\r\n <!-- Changed: Added CSS class binding to hide when no data instead of conditional rendering -->\r\n <mat-paginator \r\n #tablePaginator \r\n [pageSizeOptions]=\"config.pageSizes ?? [10, 20, 50]\" \r\n [ngClass]=\"{'paginator-hidden': !dataSource || (smallScreen && dataSource?.length === 0)}\"\r\n showFirstLastButtons>\r\n </mat-paginator>\r\n\r\n </div>\r\n \r\n <!-- Capsules -->\r\n <spa-capsules *ngIf=\"config?.viewType === 'capsule'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </spa-capsules>\r\n\r\n <!-- Cards -->\r\n <spa-cards *ngIf=\"config?.viewType === 'card'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\"\r\n (columnClick)=\"columnClicked($event.column, $event.row)\"\r\n (showBannerEvent)=\"showBanner($event)\">\r\n </spa-cards>\r\n\r\n <!-- Groups -->\r\n <spa-groups *ngIf=\"config?.viewType === 'grouped'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row, $event.group, $event.button)\">\r\n </spa-groups>\r\n\r\n <div class=\"tin-center\">\r\n <p *ngIf=\"dataSource?.length == 0\"><em>{{config.noDataMessage ?? 'No Data'}}</em></p>\r\n </div>\r\n\r\n</ng-container>\r\n\r\n\r\n<ng-container *ngIf=\"!hasFormAccess\">\r\n <div class=\"tin-center\">\r\n <p><em>Access Restricted</em></p>\r\n </div>\r\n</ng-container>\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i12$2.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i12$2.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i12$2.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i12$2.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i12$2.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i12$2.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i12$2.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i12$2.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i12$2.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i12$2.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i13.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: SearchComponent, selector: "spa-search", inputs: ["config", "smallScreen", "tableDataSource"], outputs: ["searchClick"] }, { kind: "component", type: TableHeaderComponent, selector: "app-table-header", inputs: ["lastSearch", "config", "hideTitle", "tableDataSource", "tileConfig", "smallScreen", "tileReload", "showFilterButton", "data", "tileData"], outputs: ["createClick", "customClick", "refreshClick", "tileClick", "tileUnClick"] }, { kind: "component", type: TableRowComponent, selector: "app-table-row", inputs: ["column", "row", "config", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: TableActionComponent, selector: "app-table-action", inputs: ["displayedButtons", "config", "row", "smallScreen"], outputs: ["actionClick"] }, { kind: "component", type: CapsulesComponent, selector: "spa-capsules", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "component", type: CardsComponent, selector: "spa-cards", inputs: ["config", "dataSource", "displayedButtons", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: GroupsComponent, selector: "spa-groups", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
13157
13421
  }
13158
13422
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TableInternalComponent, decorators: [{
13159
13423
  type: Component,
13160
13424
  args: [{ selector: 'spa-table-internal', standalone: false, template: "\r\n<ng-container *ngIf=\"hasFormAccess\">\r\n\r\n <!-- Search -->\r\n <spa-search\r\n *ngIf=\"config.searchConfig\" [config]=\"config.searchConfig\" [smallScreen]=\"smallScreen\" [tableDataSource]=\"tableDataSource\" style=\"margin-bottom: 20px;\" (searchClick)=\"searchClicked($event)\">\r\n </spa-search>\r\n\r\n <!-- Header -->\r\n <app-table-header\r\n [config]=\"config\" [data]=\"dataSource\" [tableDataSource]=\"tableDataSource\" [tileConfig]=\"config.tileConfig\" [tileData]=\"tileData\" [tileReload]=\"tileReload\" [lastSearch]=\"lastSearch\" [smallScreen]=\"smallScreen\"\r\n [showFilterButton]=\"showFilterButton\" (createClick)=\"newModel()\" (customClick)=\"customModel($event,null)\"\r\n (refreshClick)=\"refreshClicked()\" (tileClick)=\"tileClicked($event)\" (tileUnClick)=\"tileUnClicked($event)\">\r\n </app-table-header>\r\n\r\n\r\n <!-- Table -->\r\n <div *ngIf=\"!config.viewType || config?.viewType === 'table'\">\r\n\r\n <p *ngIf=\"!config\"><em>Configure Table</em></p>\r\n <p *ngIf=\"!dataSource\"><em>Loading...</em></p>\r\n\r\n <div *ngIf=\"dataSource && (!smallScreen || (smallScreen && dataSource?.length > 0))\">\r\n\r\n <table mat-table [dataSource]=\"tableDataSource\" [ngClass]=\"elevation\">\r\n\r\n <ng-container *ngFor=\"let column of config.columns\" [matColumnDef]=\"column.name\">\r\n <th mat-header-cell *matHeaderCellDef >{{ column.alias ?? column.name | camelToWords }}</th>\r\n <td mat-cell *matCellDef=\"let row;\" class=\"right-padding\" >\r\n\r\n <!-- Rows -->\r\n <app-table-row [column]=\"column\" [row]=\"row\" [config]=\"config\" [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked(column.name, row)\" (columnClick)=\"columnClicked(column, row)\" (showBannerEvent)=\"showBanner($event)\">\r\n </app-table-row>\r\n\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"action\">\r\n <th mat-header-cell *matHeaderCellDef> Action </th>\r\n <td mat-cell *matCellDef=\"let row\" [ngStyle]=\"{width:false ? '50px' : actionsWidth}\">\r\n <div class=\"action-buttons-container\">\r\n\r\n <!-- Actions -->\r\n <app-table-action\r\n [displayedButtons]=\"displayedButtons\" [config]=\"config\" [smallScreen]=\"smallScreen\" [row]=\"row\" (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </app-table-action>\r\n\r\n </div>\r\n </td>\r\n </ng-container>\r\n\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" [ngClass]=\"{'make-gray': (config.greyOut && config.greyOut(row)) || row.pendingApproval}\"></tr>\r\n </table>\r\n\r\n </div>\r\n\r\n <!-- Changed: Removed *ngIf condition to keep paginator always in DOM and maintain ViewChild reference -->\r\n <!-- Changed: Added CSS class binding to hide when no data instead of conditional rendering -->\r\n <mat-paginator \r\n #tablePaginator \r\n [pageSizeOptions]=\"config.pageSizes ?? [10, 20, 50]\" \r\n [ngClass]=\"{'paginator-hidden': !dataSource || (smallScreen && dataSource?.length === 0)}\"\r\n showFirstLastButtons>\r\n </mat-paginator>\r\n\r\n </div>\r\n \r\n <!-- Capsules -->\r\n <spa-capsules *ngIf=\"config?.viewType === 'capsule'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </spa-capsules>\r\n\r\n <!-- Cards -->\r\n <spa-cards *ngIf=\"config?.viewType === 'card'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\"\r\n (columnClick)=\"columnClicked($event.column, $event.row)\"\r\n (showBannerEvent)=\"showBanner($event)\">\r\n </spa-cards>\r\n\r\n <!-- Groups -->\r\n <spa-groups *ngIf=\"config?.viewType === 'grouped'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row, $event.group, $event.button)\">\r\n </spa-groups>\r\n\r\n <div class=\"tin-center\">\r\n <p *ngIf=\"dataSource?.length == 0\"><em>{{config.noDataMessage ?? 'No Data'}}</em></p>\r\n </div>\r\n\r\n</ng-container>\r\n\r\n\r\n<ng-container *ngIf=\"!hasFormAccess\">\r\n <div class=\"tin-center\">\r\n <p><em>Access Restricted</em></p>\r\n </div>\r\n</ng-container>\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"] }]
13161
- }], ctorParameters: () => [{ type: DataServiceLib }, { type: MessageService }, { type: i1$3.BreakpointObserver }, { type: i1.MatDialog }, { type: ButtonService }, { type: DialogService }, { type: TableConfigService }, { type: ConditionService }, { type: AuthService }], propDecorators: { tablePaginator: [{
13425
+ }], ctorParameters: () => [{ type: DataServiceLib }, { type: MessageService }, { type: i1$3.BreakpointObserver }, { type: i1.MatDialog }, { type: ButtonService }, { type: DialogService }, { type: TableConfigService }, { type: ConditionService }, { type: AuthService }, { type: SignalRService }], propDecorators: { tablePaginator: [{
13162
13426
  type: ViewChild,
13163
13427
  args: ['tablePaginator', { static: false }]
13164
13428
  }], data: [{
@@ -13293,6 +13557,7 @@ class DetailsDialog {
13293
13557
  this.authService = authService;
13294
13558
  this.tableConfigService = tableConfigService;
13295
13559
  this.autoRefreshEnabled = false;
13560
+ this.userKeepOpen = false; // Added: runtime state for the user-controlled Keep Open checkbox
13296
13561
  this.titleAction = "View";
13297
13562
  this.loadByAction = false;
13298
13563
  this.files = [];
@@ -13525,9 +13790,15 @@ class DetailsDialog {
13525
13790
  }
13526
13791
  this.processButtonAction(button);
13527
13792
  }
13793
+ // Added: returns the effective keepOpen value — user checkbox takes precedence when allowUserKeepOpen is enabled
13794
+ effectiveKeepOpen(button) {
13795
+ if (this.detailsConfig.allowUserKeepOpen)
13796
+ return this.userKeepOpen;
13797
+ return button.keepOpen ?? false;
13798
+ }
13528
13799
  processButtonAction(button) {
13529
13800
  if (!button.action) {
13530
- if (!button.keepOpen) {
13801
+ if (!this.effectiveKeepOpen(button)) { // Changed: use effectiveKeepOpen to respect user checkbox
13531
13802
  this.dialogRef.close({ message: 'emit', data: this.details });
13532
13803
  }
13533
13804
  return;
@@ -13607,7 +13878,7 @@ class DetailsDialog {
13607
13878
  if (button.onSuccess) {
13608
13879
  button.onSuccess(apiResponse.data, this.details);
13609
13880
  }
13610
- if (button.keepOpen) {
13881
+ if (this.effectiveKeepOpen(button)) { // Changed: use effectiveKeepOpen to respect user checkbox
13611
13882
  if (button.resetMode) {
13612
13883
  // Changed: Reset dialog to specified mode (e.g., Clear → create)
13613
13884
  this.details = Core.generateObject(this.formConfig.fields);
@@ -13616,6 +13887,10 @@ class DetailsDialog {
13616
13887
  this.isLoadComplete = true;
13617
13888
  this.setTitleAction();
13618
13889
  }
13890
+ else if (button.name === 'create' && this.detailsConfig.allowUserKeepOpen && this.userKeepOpen && this.detailsConfig.keepOpenBehavior === 'reset') {
13891
+ // Changed: User checkbox keep-open on create with 'reset' behavior — clear form for next capture, stay in create mode
13892
+ this.details = Core.generateObject(this.formConfig.fields);
13893
+ }
13619
13894
  else if (button.name === 'create' && apiResponse.data) {
13620
13895
  this.details = apiResponse.data;
13621
13896
  if (this.detailsConfig.heroField && apiResponse.data[this.detailsConfig.heroField]) {
@@ -13676,11 +13951,11 @@ class DetailsDialog {
13676
13951
  }
13677
13952
  }
13678
13953
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DetailsDialog, deps: [{ token: i1$3.BreakpointObserver }, { token: LoaderService }, { token: DataServiceLib }, { token: MessageService }, { token: i1.MatDialogRef }, { token: MAT_DIALOG_DATA }, { token: ButtonService }, { token: DialogService }, { token: AuthService }, { token: TableConfigService }], target: i0.ɵɵFactoryTarget.Component }); }
13679
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: DetailsDialog, isStandalone: false, selector: "spa-detailsDialog", outputs: { inputChange: "inputChange" }, ngImport: i0, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <spa-check *ngIf=\"detailsConfig.autoRefreshConfig\" display=\"Auto Refresh\" [(value)]=\"autoRefreshEnabled\" (valueChange)=\"toggleAutoRefresh()\"></spa-check>\r\n\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && testVisible(details,editButton.name)\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"testDisabled(details,editButton.name)\"><mat-icon>edit</mat-icon></button>\r\n </div>\r\n\r\n <button [disabled]=\"isProcessing\" *ngIf=\"loadByAction\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Refresh\" color=\"primary\" (click)=\"loadData(formConfig.loadAction, detailsConfig.causeTableRefresh)\"><mat-icon class=\"refreshIcon\">cached</mat-icon></button>\r\n \r\n <!-- Added: Top close button when position is 'top' -->\r\n <button *ngIf=\"shouldShowTopClose()\" [disabled]=\"isProcessing\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Close\" (click)=\"close()\"><mat-icon>close</mat-icon></button>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div style=\"padding-left: 24px; padding-right: 24px;\">\r\n <spa-steps *ngIf=\"stepConfig && details && stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n </div>\r\n\r\n <mat-dialog-content class=\"mat-typography dialog-scroll-content\">\r\n\r\n <spa-steps *ngIf=\"stepConfig && details && !stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && !statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && !formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n\r\n <div class=\"tin-input\" style=\"font-size:14px\">\r\n\r\n <p *ngIf=\"formConfig && !details\"><em>Loading...</em></p>\r\n\r\n <spa-form *ngIf=\"formConfig && details\" [files]=\"files\" [data]=\"details\" [config]=\"formConfig\" (inputChange)=\"inputChanged($event)\">\r\n <ng-template #dynamicSelect let-field=\"field\" let-data=\"data\" let-testReadOnly=\"testReadOnly\" let-testRequired=\"testRequired\" let-selectChanged=\"selectChanged\">\r\n <spa-select\r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [width]=\"field.width\"\r\n [nullable]=\"field.nullable\"\r\n [options]=\"field.options\"\r\n [masterOptions]=\"field.masterOptions\"\r\n [masterField]=\"field.masterField\"\r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\"\r\n [optionValue]=\"field.optionValue ?? 'value'\"\r\n [(value)]=\"data[field.name]\"\r\n [defaultFirstValue]=\"field.defaultFirstValue\"\r\n [required]=\"testRequired(field)\"\r\n [readonly]=\"testReadOnly(field)\"\r\n [hint]=\"field.hint\"\r\n [detailsConfig]=\"field.detailsConfig\"\r\n [loadAction]=\"field.loadAction\"\r\n [loadIDField]=\"field.loadIDField\"\r\n [field]=\"field\"\r\n [data]=\"data\"\r\n [infoMessage]=\"field.infoMessage\"\r\n [copyContent]=\"field.copyContent\"\r\n (valueChange)=\"selectChanged(field)\"\r\n ></spa-select>\r\n </ng-template>\r\n </spa-form>\r\n\r\n <!-- Changed: Replace mat-tab-group with spa-tabs-internal component -->\r\n <spa-tabs-internal\r\n *ngIf=\"tableConfigs && !(detailsConfig.hideTablesInCreateMode && formConfig?.mode === 'create')\"\r\n [tableConfigs]=\"tableConfigs\"\r\n [reload]=\"tableReload\"\r\n [parentDetails]=\"details\"\r\n (formRefresh)=\"loadData(formConfig.loadAction, false)\">\r\n </spa-tabs-internal>\r\n\r\n </div>\r\n\r\n </mat-dialog-content>\r\n\r\n\r\n </div>\r\n\r\n <mat-dialog-actions >\r\n\r\n <div>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='create' && createButton\" color=\"primary\"\r\n (click)=\"create()\" cdkFocusInitial>{{createButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='edit' && editButton\" color=\"primary\"\r\n (click)=\"edit()\" cdkFocusInitial>{{editButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <ng-container *ngFor=\"let btn of extraButtons\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && testVisible(details,btn.name)\" mat-stroked-button [disabled]=\"isProcessing || testDisabled(details,btn.name)\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\" (click)=\"custom(btn)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"btn.icon\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\">{{btn.icon.name}}</mat-icon>\r\n {{btn.display ?? btn.name | titlecase}}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Changed: Bottom close button now uses conditional display and custom text -->\r\n <button *ngIf=\"shouldShowBottomClose()\" mat-stroked-button color=\"primary\" (click)=\"close()\">{{getCloseText()}}</button>\r\n\r\n </div>\r\n\r\n <div class=\"col d-flex justify-content-end\" *ngIf=\"smallScreen\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Delete\" [disabled]=\"isProcessing\" style=\"color: red;\" (click)=\"delete()\" *ngIf=\"formConfig.mode!='create' && deleteButton\"><mat-icon>delete</mat-icon></button>\r\n </div>\r\n\r\n\r\n </mat-dialog-actions>\r\n\r\n\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto;max-height:none!important;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input{flex:1;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input>spa-tabs-internal{flex:1;display:flex;flex-direction:column}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: i14$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: CheckComponent, selector: "spa-check", inputs: ["readonly", "display", "value", "infoMessage"], outputs: ["valueChange", "click", "check", "uncheck", "infoClick"] }, { kind: "component", type: SelectComponent, selector: "spa-select", inputs: ["detailsConfig"] }, { kind: "component", type: StepsComponent, selector: "spa-steps", inputs: ["value", "config", "data"] }, { kind: "component", type: FormComponent, selector: "spa-form", inputs: ["files", "data", "config"], outputs: ["buttonClick", "inputChange"] }, { kind: "component", type: AlertComponent, selector: "spa-alert", inputs: ["config", "data"] }, { kind: "component", type: TabsInternalComponent, selector: "spa-tabs-internal", inputs: ["tableConfigs", "reload", "parentDetails"], outputs: ["formRefresh"] }, { kind: "component", type: StatusesComponent, selector: "spa-statuses", inputs: ["config", "data"] }, { kind: "pipe", type: i2.TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
13954
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: DetailsDialog, isStandalone: false, selector: "spa-detailsDialog", outputs: { inputChange: "inputChange" }, ngImport: i0, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <spa-check *ngIf=\"detailsConfig.autoRefreshConfig\" display=\"Auto Refresh\" [(value)]=\"autoRefreshEnabled\" (valueChange)=\"toggleAutoRefresh()\"></spa-check>\r\n <!-- Added: Keep Open checkbox \u2014 visible when developer enables allowUserKeepOpen; user controls whether dialog closes after action -->\r\n <spa-check *ngIf=\"detailsConfig.allowUserKeepOpen\" display=\"Keep Open\" [(value)]=\"userKeepOpen\"></spa-check>\r\n\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && testVisible(details,editButton.name)\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"testDisabled(details,editButton.name)\"><mat-icon>edit</mat-icon></button>\r\n </div>\r\n\r\n <button [disabled]=\"isProcessing\" *ngIf=\"loadByAction\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Refresh\" color=\"primary\" (click)=\"loadData(formConfig.loadAction, detailsConfig.causeTableRefresh)\"><mat-icon class=\"refreshIcon\">cached</mat-icon></button>\r\n \r\n <!-- Added: Top close button when position is 'top' -->\r\n <button *ngIf=\"shouldShowTopClose()\" [disabled]=\"isProcessing\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Close\" (click)=\"close()\"><mat-icon>close</mat-icon></button>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div style=\"padding-left: 24px; padding-right: 24px;\">\r\n <spa-steps *ngIf=\"stepConfig && details && stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n </div>\r\n\r\n <mat-dialog-content class=\"mat-typography dialog-scroll-content\">\r\n\r\n <spa-steps *ngIf=\"stepConfig && details && !stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && !statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && !formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n\r\n <div class=\"tin-input\" style=\"font-size:14px\">\r\n\r\n <p *ngIf=\"formConfig && !details\"><em>Loading...</em></p>\r\n\r\n <spa-form *ngIf=\"formConfig && details\" [files]=\"files\" [data]=\"details\" [config]=\"formConfig\" (inputChange)=\"inputChanged($event)\">\r\n <ng-template #dynamicSelect let-field=\"field\" let-data=\"data\" let-testReadOnly=\"testReadOnly\" let-testRequired=\"testRequired\" let-selectChanged=\"selectChanged\">\r\n <spa-select\r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [width]=\"field.width\"\r\n [nullable]=\"field.nullable\"\r\n [options]=\"field.options\"\r\n [masterOptions]=\"field.masterOptions\"\r\n [masterField]=\"field.masterField\"\r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\"\r\n [optionValue]=\"field.optionValue ?? 'value'\"\r\n [(value)]=\"data[field.name]\"\r\n [defaultFirstValue]=\"field.defaultFirstValue\"\r\n [required]=\"testRequired(field)\"\r\n [readonly]=\"testReadOnly(field)\"\r\n [hint]=\"field.hint\"\r\n [detailsConfig]=\"field.detailsConfig\"\r\n [loadAction]=\"field.loadAction\"\r\n [loadIDField]=\"field.loadIDField\"\r\n [field]=\"field\"\r\n [data]=\"data\"\r\n [infoMessage]=\"field.infoMessage\"\r\n [copyContent]=\"field.copyContent\"\r\n (valueChange)=\"selectChanged(field)\"\r\n ></spa-select>\r\n </ng-template>\r\n </spa-form>\r\n\r\n <!-- Changed: Replace mat-tab-group with spa-tabs-internal component -->\r\n <spa-tabs-internal\r\n *ngIf=\"tableConfigs && !(detailsConfig.hideTablesInCreateMode && formConfig?.mode === 'create')\"\r\n [tableConfigs]=\"tableConfigs\"\r\n [reload]=\"tableReload\"\r\n [parentDetails]=\"details\"\r\n (formRefresh)=\"loadData(formConfig.loadAction, false)\">\r\n </spa-tabs-internal>\r\n\r\n </div>\r\n\r\n </mat-dialog-content>\r\n\r\n\r\n </div>\r\n\r\n <mat-dialog-actions >\r\n\r\n <div>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='create' && createButton\" color=\"primary\"\r\n (click)=\"create()\" cdkFocusInitial>{{createButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='edit' && editButton\" color=\"primary\"\r\n (click)=\"edit()\" cdkFocusInitial>{{editButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <ng-container *ngFor=\"let btn of extraButtons\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && testVisible(details,btn.name)\" mat-stroked-button [disabled]=\"isProcessing || testDisabled(details,btn.name)\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\" (click)=\"custom(btn)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"btn.icon\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\">{{btn.icon.name}}</mat-icon>\r\n {{btn.display ?? btn.name | titlecase}}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Changed: Bottom close button now uses conditional display and custom text -->\r\n <button *ngIf=\"shouldShowBottomClose()\" mat-stroked-button color=\"primary\" (click)=\"close()\">{{getCloseText()}}</button>\r\n\r\n </div>\r\n\r\n <div class=\"col d-flex justify-content-end\" *ngIf=\"smallScreen\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Delete\" [disabled]=\"isProcessing\" style=\"color: red;\" (click)=\"delete()\" *ngIf=\"formConfig.mode!='create' && deleteButton\"><mat-icon>delete</mat-icon></button>\r\n </div>\r\n\r\n\r\n </mat-dialog-actions>\r\n\r\n\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto;max-height:none!important;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input{flex:1;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input>spa-tabs-internal{flex:1;display:flex;flex-direction:column}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: i14$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: CheckComponent, selector: "spa-check", inputs: ["readonly", "display", "value", "infoMessage"], outputs: ["valueChange", "click", "check", "uncheck", "infoClick"] }, { kind: "component", type: SelectComponent, selector: "spa-select", inputs: ["detailsConfig"] }, { kind: "component", type: StepsComponent, selector: "spa-steps", inputs: ["value", "config", "data"] }, { kind: "component", type: FormComponent, selector: "spa-form", inputs: ["files", "data", "config"], outputs: ["buttonClick", "inputChange"] }, { kind: "component", type: AlertComponent, selector: "spa-alert", inputs: ["config", "data"] }, { kind: "component", type: TabsInternalComponent, selector: "spa-tabs-internal", inputs: ["tableConfigs", "reload", "parentDetails"], outputs: ["formRefresh"] }, { kind: "component", type: StatusesComponent, selector: "spa-statuses", inputs: ["config", "data"] }, { kind: "pipe", type: i2.TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
13680
13955
  }
13681
13956
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DetailsDialog, decorators: [{
13682
13957
  type: Component,
13683
- args: [{ selector: 'spa-detailsDialog', standalone: false, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <spa-check *ngIf=\"detailsConfig.autoRefreshConfig\" display=\"Auto Refresh\" [(value)]=\"autoRefreshEnabled\" (valueChange)=\"toggleAutoRefresh()\"></spa-check>\r\n\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && testVisible(details,editButton.name)\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"testDisabled(details,editButton.name)\"><mat-icon>edit</mat-icon></button>\r\n </div>\r\n\r\n <button [disabled]=\"isProcessing\" *ngIf=\"loadByAction\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Refresh\" color=\"primary\" (click)=\"loadData(formConfig.loadAction, detailsConfig.causeTableRefresh)\"><mat-icon class=\"refreshIcon\">cached</mat-icon></button>\r\n \r\n <!-- Added: Top close button when position is 'top' -->\r\n <button *ngIf=\"shouldShowTopClose()\" [disabled]=\"isProcessing\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Close\" (click)=\"close()\"><mat-icon>close</mat-icon></button>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div style=\"padding-left: 24px; padding-right: 24px;\">\r\n <spa-steps *ngIf=\"stepConfig && details && stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n </div>\r\n\r\n <mat-dialog-content class=\"mat-typography dialog-scroll-content\">\r\n\r\n <spa-steps *ngIf=\"stepConfig && details && !stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && !statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && !formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n\r\n <div class=\"tin-input\" style=\"font-size:14px\">\r\n\r\n <p *ngIf=\"formConfig && !details\"><em>Loading...</em></p>\r\n\r\n <spa-form *ngIf=\"formConfig && details\" [files]=\"files\" [data]=\"details\" [config]=\"formConfig\" (inputChange)=\"inputChanged($event)\">\r\n <ng-template #dynamicSelect let-field=\"field\" let-data=\"data\" let-testReadOnly=\"testReadOnly\" let-testRequired=\"testRequired\" let-selectChanged=\"selectChanged\">\r\n <spa-select\r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [width]=\"field.width\"\r\n [nullable]=\"field.nullable\"\r\n [options]=\"field.options\"\r\n [masterOptions]=\"field.masterOptions\"\r\n [masterField]=\"field.masterField\"\r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\"\r\n [optionValue]=\"field.optionValue ?? 'value'\"\r\n [(value)]=\"data[field.name]\"\r\n [defaultFirstValue]=\"field.defaultFirstValue\"\r\n [required]=\"testRequired(field)\"\r\n [readonly]=\"testReadOnly(field)\"\r\n [hint]=\"field.hint\"\r\n [detailsConfig]=\"field.detailsConfig\"\r\n [loadAction]=\"field.loadAction\"\r\n [loadIDField]=\"field.loadIDField\"\r\n [field]=\"field\"\r\n [data]=\"data\"\r\n [infoMessage]=\"field.infoMessage\"\r\n [copyContent]=\"field.copyContent\"\r\n (valueChange)=\"selectChanged(field)\"\r\n ></spa-select>\r\n </ng-template>\r\n </spa-form>\r\n\r\n <!-- Changed: Replace mat-tab-group with spa-tabs-internal component -->\r\n <spa-tabs-internal\r\n *ngIf=\"tableConfigs && !(detailsConfig.hideTablesInCreateMode && formConfig?.mode === 'create')\"\r\n [tableConfigs]=\"tableConfigs\"\r\n [reload]=\"tableReload\"\r\n [parentDetails]=\"details\"\r\n (formRefresh)=\"loadData(formConfig.loadAction, false)\">\r\n </spa-tabs-internal>\r\n\r\n </div>\r\n\r\n </mat-dialog-content>\r\n\r\n\r\n </div>\r\n\r\n <mat-dialog-actions >\r\n\r\n <div>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='create' && createButton\" color=\"primary\"\r\n (click)=\"create()\" cdkFocusInitial>{{createButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='edit' && editButton\" color=\"primary\"\r\n (click)=\"edit()\" cdkFocusInitial>{{editButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <ng-container *ngFor=\"let btn of extraButtons\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && testVisible(details,btn.name)\" mat-stroked-button [disabled]=\"isProcessing || testDisabled(details,btn.name)\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\" (click)=\"custom(btn)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"btn.icon\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\">{{btn.icon.name}}</mat-icon>\r\n {{btn.display ?? btn.name | titlecase}}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Changed: Bottom close button now uses conditional display and custom text -->\r\n <button *ngIf=\"shouldShowBottomClose()\" mat-stroked-button color=\"primary\" (click)=\"close()\">{{getCloseText()}}</button>\r\n\r\n </div>\r\n\r\n <div class=\"col d-flex justify-content-end\" *ngIf=\"smallScreen\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Delete\" [disabled]=\"isProcessing\" style=\"color: red;\" (click)=\"delete()\" *ngIf=\"formConfig.mode!='create' && deleteButton\"><mat-icon>delete</mat-icon></button>\r\n </div>\r\n\r\n\r\n </mat-dialog-actions>\r\n\r\n\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto;max-height:none!important;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input{flex:1;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input>spa-tabs-internal{flex:1;display:flex;flex-direction:column}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"] }]
13958
+ args: [{ selector: 'spa-detailsDialog', standalone: false, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <spa-check *ngIf=\"detailsConfig.autoRefreshConfig\" display=\"Auto Refresh\" [(value)]=\"autoRefreshEnabled\" (valueChange)=\"toggleAutoRefresh()\"></spa-check>\r\n <!-- Added: Keep Open checkbox \u2014 visible when developer enables allowUserKeepOpen; user controls whether dialog closes after action -->\r\n <spa-check *ngIf=\"detailsConfig.allowUserKeepOpen\" display=\"Keep Open\" [(value)]=\"userKeepOpen\"></spa-check>\r\n\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && testVisible(details,editButton.name)\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"testDisabled(details,editButton.name)\"><mat-icon>edit</mat-icon></button>\r\n </div>\r\n\r\n <button [disabled]=\"isProcessing\" *ngIf=\"loadByAction\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Refresh\" color=\"primary\" (click)=\"loadData(formConfig.loadAction, detailsConfig.causeTableRefresh)\"><mat-icon class=\"refreshIcon\">cached</mat-icon></button>\r\n \r\n <!-- Added: Top close button when position is 'top' -->\r\n <button *ngIf=\"shouldShowTopClose()\" [disabled]=\"isProcessing\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Close\" (click)=\"close()\"><mat-icon>close</mat-icon></button>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div style=\"padding-left: 24px; padding-right: 24px;\">\r\n <spa-steps *ngIf=\"stepConfig && details && stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n </div>\r\n\r\n <mat-dialog-content class=\"mat-typography dialog-scroll-content\">\r\n\r\n <spa-steps *ngIf=\"stepConfig && details && !stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && !statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && !formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n\r\n <div class=\"tin-input\" style=\"font-size:14px\">\r\n\r\n <p *ngIf=\"formConfig && !details\"><em>Loading...</em></p>\r\n\r\n <spa-form *ngIf=\"formConfig && details\" [files]=\"files\" [data]=\"details\" [config]=\"formConfig\" (inputChange)=\"inputChanged($event)\">\r\n <ng-template #dynamicSelect let-field=\"field\" let-data=\"data\" let-testReadOnly=\"testReadOnly\" let-testRequired=\"testRequired\" let-selectChanged=\"selectChanged\">\r\n <spa-select\r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [width]=\"field.width\"\r\n [nullable]=\"field.nullable\"\r\n [options]=\"field.options\"\r\n [masterOptions]=\"field.masterOptions\"\r\n [masterField]=\"field.masterField\"\r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\"\r\n [optionValue]=\"field.optionValue ?? 'value'\"\r\n [(value)]=\"data[field.name]\"\r\n [defaultFirstValue]=\"field.defaultFirstValue\"\r\n [required]=\"testRequired(field)\"\r\n [readonly]=\"testReadOnly(field)\"\r\n [hint]=\"field.hint\"\r\n [detailsConfig]=\"field.detailsConfig\"\r\n [loadAction]=\"field.loadAction\"\r\n [loadIDField]=\"field.loadIDField\"\r\n [field]=\"field\"\r\n [data]=\"data\"\r\n [infoMessage]=\"field.infoMessage\"\r\n [copyContent]=\"field.copyContent\"\r\n (valueChange)=\"selectChanged(field)\"\r\n ></spa-select>\r\n </ng-template>\r\n </spa-form>\r\n\r\n <!-- Changed: Replace mat-tab-group with spa-tabs-internal component -->\r\n <spa-tabs-internal\r\n *ngIf=\"tableConfigs && !(detailsConfig.hideTablesInCreateMode && formConfig?.mode === 'create')\"\r\n [tableConfigs]=\"tableConfigs\"\r\n [reload]=\"tableReload\"\r\n [parentDetails]=\"details\"\r\n (formRefresh)=\"loadData(formConfig.loadAction, false)\">\r\n </spa-tabs-internal>\r\n\r\n </div>\r\n\r\n </mat-dialog-content>\r\n\r\n\r\n </div>\r\n\r\n <mat-dialog-actions >\r\n\r\n <div>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='create' && createButton\" color=\"primary\"\r\n (click)=\"create()\" cdkFocusInitial>{{createButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='edit' && editButton\" color=\"primary\"\r\n (click)=\"edit()\" cdkFocusInitial>{{editButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <ng-container *ngFor=\"let btn of extraButtons\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && testVisible(details,btn.name)\" mat-stroked-button [disabled]=\"isProcessing || testDisabled(details,btn.name)\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\" (click)=\"custom(btn)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"btn.icon\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\">{{btn.icon.name}}</mat-icon>\r\n {{btn.display ?? btn.name | titlecase}}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Changed: Bottom close button now uses conditional display and custom text -->\r\n <button *ngIf=\"shouldShowBottomClose()\" mat-stroked-button color=\"primary\" (click)=\"close()\">{{getCloseText()}}</button>\r\n\r\n </div>\r\n\r\n <div class=\"col d-flex justify-content-end\" *ngIf=\"smallScreen\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Delete\" [disabled]=\"isProcessing\" style=\"color: red;\" (click)=\"delete()\" *ngIf=\"formConfig.mode!='create' && deleteButton\"><mat-icon>delete</mat-icon></button>\r\n </div>\r\n\r\n\r\n </mat-dialog-actions>\r\n\r\n\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto;max-height:none!important;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input{flex:1;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input>spa-tabs-internal{flex:1;display:flex;flex-direction:column}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"] }]
13684
13959
  }], ctorParameters: () => [{ type: i1$3.BreakpointObserver }, { type: LoaderService }, { type: DataServiceLib }, { type: MessageService }, { type: i1.MatDialogRef }, { type: DetailsDialogConfig, decorators: [{
13685
13960
  type: Inject,
13686
13961
  args: [MAT_DIALOG_DATA]
@@ -13690,7 +13965,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
13690
13965
  ;
13691
13966
 
13692
13967
  class TableComponent {
13693
- constructor(dataService, messageService, breakpointObserver, dialog, buttonService, dialogService, tableConfigService, conditionService, authService) {
13968
+ constructor(dataService, messageService, breakpointObserver, dialog, buttonService, dialogService, tableConfigService, conditionService, authService, signalRService) {
13694
13969
  this.dataService = dataService;
13695
13970
  this.messageService = messageService;
13696
13971
  this.breakpointObserver = breakpointObserver;
@@ -13700,6 +13975,7 @@ class TableComponent {
13700
13975
  this.tableConfigService = tableConfigService;
13701
13976
  this.conditionService = conditionService;
13702
13977
  this.authService = authService;
13978
+ this.signalRService = signalRService;
13703
13979
  this.elevation = "mat-elevation-z5";
13704
13980
  this.actionsWidth = "50px";
13705
13981
  this.showFilterButton = true;
@@ -13721,6 +13997,10 @@ class TableComponent {
13721
13997
  this.actionClick = new EventEmitter();
13722
13998
  this.inputChange = new EventEmitter();
13723
13999
  this.actionResponse = new EventEmitter();
14000
+ // Changed: Real-time SignalR subscription management
14001
+ this.realTimeSubs = [];
14002
+ this.realTimeEntityName = '';
14003
+ this.pendingRealTimeUpdate = false; // Changed: Flag to skip refreshClicked when SignalR handles it
13724
14004
  // detect screen size changes
13725
14005
  this.breakpointObserver.observe(["(max-width: 600px)"]).subscribe((result) => {
13726
14006
  this.smallScreen = result.matches;
@@ -13760,6 +14040,8 @@ class TableComponent {
13760
14040
  this.dataSource = this.data;
13761
14041
  this.tableDataSource = new MatTableDataSource(this.dataSource);
13762
14042
  this.setPaginator(); // Set paginator after datasource initialization
14043
+ if (this.config.realTime)
14044
+ this.setupRealTimeSubscriptions(); // Changed: Subscribe to SignalR entity changes
13763
14045
  }
13764
14046
  updateTableConfiguration() {
13765
14047
  this.elevation = this.getElevationClass(this.config?.elevation);
@@ -13986,12 +14268,16 @@ class TableComponent {
13986
14268
  const buttonName = button.name;
13987
14269
  let btn = {
13988
14270
  ...button,
13989
- detailsConfig: button.detailsConfig ? button.detailsConfig : {
13990
- formConfig: this.config.formConfig,
13991
- stepConfig: this.config.stepConfig,
13992
- buttons: this.config.buttons,
13993
- heroField: this.config.heroField,
13994
- heroValue: this.config.heroValue,
14271
+ detailsConfig: {
14272
+ ...(button.detailsConfig ?? {
14273
+ formConfig: this.config.formConfig,
14274
+ stepConfig: this.config.stepConfig,
14275
+ buttons: this.config.buttons,
14276
+ heroField: this.config.heroField,
14277
+ heroValue: this.config.heroValue,
14278
+ }),
14279
+ allowUserKeepOpen: this.config.allowUserKeepOpen, // Changed: table config is the single source of truth for this setting
14280
+ keepOpenBehavior: this.config.keepOpenBehavior, // Changed: propagate create behavior preference from table config
13995
14281
  }
13996
14282
  };
13997
14283
  this.dialogService.openConfiguredDetailsDialog(btn, row, DetailsDialog).subscribe(result => {
@@ -14005,7 +14291,7 @@ class TableComponent {
14005
14291
  else if (result.message === 'success') {
14006
14292
  this.actionClickedEmit(buttonName, result.data);
14007
14293
  this.actionResponse.emit({ name: buttonName, data: result.data });
14008
- this.refreshClicked();
14294
+ this.realTimeRefreshOrFallback(); // Changed: Use real-time update if enabled, otherwise refresh
14009
14295
  this.actionSuccess.emit();
14010
14296
  if (buttonName == 'create' && btn.editOnSuccess) {
14011
14297
  this.editModel(result.data.data);
@@ -14071,7 +14357,7 @@ class TableComponent {
14071
14357
  else {
14072
14358
  this.messageService.toast("Updated");
14073
14359
  }
14074
- this.refreshClicked();
14360
+ this.realTimeRefreshOrFallback(); // Changed: Use real-time update if enabled, otherwise refresh
14075
14361
  this.actionSuccess.emit();
14076
14362
  }
14077
14363
  else {
@@ -14100,13 +14386,87 @@ class TableComponent {
14100
14386
  return "mat-elevation-z5";
14101
14387
  }
14102
14388
  }
14103
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TableComponent, deps: [{ token: DataServiceLib }, { token: MessageService }, { token: i1$3.BreakpointObserver }, { token: i1.MatDialog }, { token: ButtonService }, { token: DialogService }, { token: TableConfigService }, { token: ConditionService }, { token: AuthService }], target: i0.ɵɵFactoryTarget.Component }); }
14104
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TableComponent, isStandalone: false, selector: "spa-table", inputs: { data: "data", tileData: "tileData", config: "config", reload: "reload", activeTab: "activeTab", inTab: "inTab" }, outputs: { dataLoad: "dataLoad", actionSuccess: "actionSuccess", refreshClick: "refreshClick", searchClick: "searchClick", createClick: "createClick", actionClick: "actionClick", inputChange: "inputChange", actionResponse: "actionResponse" }, viewQueries: [{ propertyName: "tablePaginator", first: true, predicate: ["tablePaginator"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "\r\n<ng-container *ngIf=\"hasFormAccess\">\r\n\r\n <!-- Search -->\r\n <spa-search\r\n *ngIf=\"config.searchConfig\" [config]=\"config.searchConfig\" [smallScreen]=\"smallScreen\" [tableDataSource]=\"tableDataSource\" style=\"margin-bottom: 20px;\" (searchClick)=\"searchClicked($event)\">\r\n </spa-search>\r\n\r\n <!-- Header -->\r\n <app-table-header\r\n [config]=\"config\" [data]=\"dataSource\" [tableDataSource]=\"tableDataSource\" [tileConfig]=\"config.tileConfig\" [tileData]=\"tileData\" [tileReload]=\"tileReload\" [lastSearch]=\"lastSearch\" [smallScreen]=\"smallScreen\"\r\n [showFilterButton]=\"showFilterButton\" (createClick)=\"newModel()\" (customClick)=\"customModel($event,null)\"\r\n (refreshClick)=\"refreshClicked()\" (tileClick)=\"tileClicked($event)\" (tileUnClick)=\"tileUnClicked($event)\">\r\n </app-table-header>\r\n\r\n\r\n <!-- Table -->\r\n <div *ngIf=\"!config.viewType || config?.viewType === 'table'\">\r\n\r\n <p *ngIf=\"!config\"><em>Configure Table</em></p>\r\n <p *ngIf=\"!dataSource\"><em>Loading...</em></p>\r\n\r\n <div *ngIf=\"dataSource && (!smallScreen || (smallScreen && dataSource?.length > 0))\">\r\n\r\n <table mat-table [dataSource]=\"tableDataSource\" [ngClass]=\"elevation\">\r\n\r\n <ng-container *ngFor=\"let column of config.columns\" [matColumnDef]=\"column.name\">\r\n <th mat-header-cell *matHeaderCellDef >{{ column.alias ?? column.name | camelToWords }}</th>\r\n <td mat-cell *matCellDef=\"let row;\" class=\"right-padding\" >\r\n\r\n <!-- Rows -->\r\n <app-table-row [column]=\"column\" [row]=\"row\" [config]=\"config\" [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked(column.name, row)\" (columnClick)=\"columnClicked(column, row)\" (showBannerEvent)=\"showBanner($event)\">\r\n </app-table-row>\r\n\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"action\">\r\n <th mat-header-cell *matHeaderCellDef> Action </th>\r\n <td mat-cell *matCellDef=\"let row\" [ngStyle]=\"{width:false ? '20px' : actionsWidth}\">\r\n <div class=\"action-buttons-container\">\r\n\r\n <!-- Actions -->\r\n <app-table-action\r\n [displayedButtons]=\"displayedButtons\" [config]=\"config\" [smallScreen]=\"smallScreen\" [row]=\"row\" (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </app-table-action>\r\n\r\n </div>\r\n </td>\r\n </ng-container>\r\n\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" [ngClass]=\"{'make-gray': (config.greyOut && config.greyOut(row)) || row.pendingApproval}\"></tr>\r\n </table>\r\n\r\n </div>\r\n\r\n <!-- Changed: Removed *ngIf condition to keep paginator always in DOM and maintain ViewChild reference -->\r\n <!-- Changed: Added CSS class binding to hide when no data instead of conditional rendering -->\r\n <mat-paginator \r\n #tablePaginator \r\n [pageSizeOptions]=\"config.pageSizes ?? [10, 20, 50]\" \r\n [ngClass]=\"{'paginator-hidden': !dataSource || (smallScreen && dataSource?.length === 0)}\"\r\n showFirstLastButtons>\r\n </mat-paginator>\r\n\r\n </div>\r\n \r\n <!-- Capsules -->\r\n <spa-capsules *ngIf=\"config?.viewType === 'capsule'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </spa-capsules>\r\n\r\n\r\n <!-- Cards -->\r\n <spa-cards *ngIf=\"config?.viewType === 'card'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\"\r\n (columnClick)=\"columnClicked($event.column, $event.row)\"\r\n (showBannerEvent)=\"showBanner($event)\">\r\n </spa-cards>\r\n\r\n <!-- Groups - Added: New grouped view type -->\r\n <spa-groups *ngIf=\"config?.viewType === 'grouped'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row, $event.group, $event.button)\">\r\n </spa-groups>\r\n\r\n\r\n <div class=\"tin-center\">\r\n <p *ngIf=\"dataSource?.length == 0\"><em>{{config.noDataMessage ?? 'No Data'}}</em></p>\r\n </div>\r\n\r\n</ng-container>\r\n\r\n\r\n<ng-container *ngIf=\"!hasFormAccess\">\r\n <div class=\"tin-center\">\r\n <p><em>Access Restricted</em></p>\r\n </div>\r\n</ng-container>\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto;max-height:none!important;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input{flex:1;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input>spa-tabs-internal{flex:1;display:flex;flex-direction:column}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i11.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i11.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i11.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i11.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i11.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i11.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i11.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i11.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i11.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i11.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i12$2.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: SearchComponent, selector: "spa-search", inputs: ["config", "smallScreen", "tableDataSource"], outputs: ["searchClick"] }, { kind: "component", type: TableHeaderComponent, selector: "app-table-header", inputs: ["lastSearch", "config", "hideTitle", "tableDataSource", "tileConfig", "smallScreen", "tileReload", "showFilterButton", "data", "tileData"], outputs: ["createClick", "customClick", "refreshClick", "tileClick", "tileUnClick"] }, { kind: "component", type: TableRowComponent, selector: "app-table-row", inputs: ["column", "row", "config", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: TableActionComponent, selector: "app-table-action", inputs: ["displayedButtons", "config", "row", "smallScreen"], outputs: ["actionClick"] }, { kind: "component", type: CapsulesComponent, selector: "spa-capsules", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "component", type: CardsComponent, selector: "spa-cards", inputs: ["config", "dataSource", "displayedButtons", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: GroupsComponent, selector: "spa-groups", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
14389
+ // Changed: Skip refresh if real-time is enabled SignalR will handle it. 3s safety fallback.
14390
+ realTimeRefreshOrFallback() {
14391
+ if (!this.config.realTime) {
14392
+ this.refreshClicked();
14393
+ return;
14394
+ }
14395
+ this.pendingRealTimeUpdate = false;
14396
+ setTimeout(() => {
14397
+ if (!this.pendingRealTimeUpdate)
14398
+ this.refreshClicked(); // Fallback if SignalR event didn't arrive
14399
+ }, 3000);
14400
+ }
14401
+ // Changed: Set up SignalR subscriptions for real-time entity change updates
14402
+ setupRealTimeSubscriptions() {
14403
+ this.realTimeEntityName = this.config.entityName || this.deriveEntityName();
14404
+ // Changed: Subscribe to entity created — add new row to table
14405
+ this.realTimeSubs.push(this.signalRService.entityCreated$.subscribe(event => {
14406
+ if (event.entityName !== this.realTimeEntityName)
14407
+ return;
14408
+ if (!this.tableDataSource)
14409
+ return;
14410
+ this.pendingRealTimeUpdate = true;
14411
+ const data = [...this.tableDataSource.data, event.data];
14412
+ this.tableDataSource.data = data;
14413
+ this.dataSource = data;
14414
+ }));
14415
+ // Changed: Subscribe to entity updated — replace existing row by ID
14416
+ this.realTimeSubs.push(this.signalRService.entityUpdated$.subscribe(event => {
14417
+ if (event.entityName !== this.realTimeEntityName)
14418
+ return;
14419
+ if (!this.tableDataSource)
14420
+ return;
14421
+ this.pendingRealTimeUpdate = true;
14422
+ const idKey = this.findIdKey(event.data);
14423
+ if (!idKey)
14424
+ return;
14425
+ const data = this.tableDataSource.data.map(row => row[idKey] === event.data[idKey] ? event.data : row);
14426
+ this.tableDataSource.data = data;
14427
+ this.dataSource = data;
14428
+ }));
14429
+ // Changed: Subscribe to entity deleted — remove row by ID
14430
+ this.realTimeSubs.push(this.signalRService.entityDeleted$.subscribe(event => {
14431
+ if (event.entityName !== this.realTimeEntityName)
14432
+ return;
14433
+ if (!this.tableDataSource)
14434
+ return;
14435
+ this.pendingRealTimeUpdate = true;
14436
+ const idKey = this.findIdKey(this.tableDataSource.data[0]);
14437
+ if (!idKey)
14438
+ return;
14439
+ const data = this.tableDataSource.data.filter(row => row[idKey] !== event.data);
14440
+ this.tableDataSource.data = data;
14441
+ this.dataSource = data;
14442
+ }));
14443
+ }
14444
+ // Changed: Derive entity name from loadAction URL (e.g. 'products/all/x' → 'Product')
14445
+ deriveEntityName() {
14446
+ const url = this.config.loadAction?.url || '';
14447
+ const segment = url.split('/')[0]; // First URL segment (e.g. 'products')
14448
+ if (!segment)
14449
+ return '';
14450
+ const name = segment.charAt(0).toUpperCase() + segment.slice(1); // Capitalize
14451
+ return name.endsWith('s') ? name.slice(0, -1) : name; // Remove trailing 's' for singular
14452
+ }
14453
+ // Changed: Find the ID property key in a row object (convention: ends with 'ID')
14454
+ findIdKey(row) {
14455
+ if (!row)
14456
+ return null;
14457
+ return Object.keys(row).find(k => k.endsWith('ID') && k !== 'TenantID') || null;
14458
+ }
14459
+ // Changed: Clean up SignalR subscriptions on component destroy
14460
+ ngOnDestroy() {
14461
+ this.realTimeSubs.forEach(s => s.unsubscribe());
14462
+ }
14463
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TableComponent, deps: [{ token: DataServiceLib }, { token: MessageService }, { token: i1$3.BreakpointObserver }, { token: i1.MatDialog }, { token: ButtonService }, { token: DialogService }, { token: TableConfigService }, { token: ConditionService }, { token: AuthService }, { token: SignalRService }], target: i0.ɵɵFactoryTarget.Component }); }
14464
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TableComponent, isStandalone: false, selector: "spa-table", inputs: { data: "data", tileData: "tileData", config: "config", reload: "reload", activeTab: "activeTab", inTab: "inTab" }, outputs: { dataLoad: "dataLoad", actionSuccess: "actionSuccess", refreshClick: "refreshClick", searchClick: "searchClick", createClick: "createClick", actionClick: "actionClick", inputChange: "inputChange", actionResponse: "actionResponse" }, viewQueries: [{ propertyName: "tablePaginator", first: true, predicate: ["tablePaginator"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "\r\n<ng-container *ngIf=\"hasFormAccess\">\r\n\r\n <!-- Search -->\r\n <spa-search\r\n *ngIf=\"config.searchConfig\" [config]=\"config.searchConfig\" [smallScreen]=\"smallScreen\" [tableDataSource]=\"tableDataSource\" style=\"margin-bottom: 20px;\" (searchClick)=\"searchClicked($event)\">\r\n </spa-search>\r\n\r\n <!-- Header -->\r\n <app-table-header\r\n [config]=\"config\" [data]=\"dataSource\" [tableDataSource]=\"tableDataSource\" [tileConfig]=\"config.tileConfig\" [tileData]=\"tileData\" [tileReload]=\"tileReload\" [lastSearch]=\"lastSearch\" [smallScreen]=\"smallScreen\"\r\n [showFilterButton]=\"showFilterButton\" (createClick)=\"newModel()\" (customClick)=\"customModel($event,null)\"\r\n (refreshClick)=\"refreshClicked()\" (tileClick)=\"tileClicked($event)\" (tileUnClick)=\"tileUnClicked($event)\">\r\n </app-table-header>\r\n\r\n\r\n <!-- Table -->\r\n <div *ngIf=\"!config.viewType || config?.viewType === 'table'\">\r\n\r\n <p *ngIf=\"!config\"><em>Configure Table</em></p>\r\n <p *ngIf=\"!dataSource\"><em>Loading...</em></p>\r\n\r\n <div *ngIf=\"dataSource && (!smallScreen || (smallScreen && dataSource?.length > 0))\">\r\n\r\n <table mat-table [dataSource]=\"tableDataSource\" [ngClass]=\"elevation\">\r\n\r\n <ng-container *ngFor=\"let column of config.columns\" [matColumnDef]=\"column.name\">\r\n <th mat-header-cell *matHeaderCellDef >{{ column.alias ?? column.name | camelToWords }}</th>\r\n <td mat-cell *matCellDef=\"let row;\" class=\"right-padding\" >\r\n\r\n <!-- Rows -->\r\n <app-table-row [column]=\"column\" [row]=\"row\" [config]=\"config\" [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked(column.name, row)\" (columnClick)=\"columnClicked(column, row)\" (showBannerEvent)=\"showBanner($event)\">\r\n </app-table-row>\r\n\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"action\">\r\n <th mat-header-cell *matHeaderCellDef> Action </th>\r\n <td mat-cell *matCellDef=\"let row\" [ngStyle]=\"{width:false ? '20px' : actionsWidth}\">\r\n <div class=\"action-buttons-container\">\r\n\r\n <!-- Actions -->\r\n <app-table-action\r\n [displayedButtons]=\"displayedButtons\" [config]=\"config\" [smallScreen]=\"smallScreen\" [row]=\"row\" (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </app-table-action>\r\n\r\n </div>\r\n </td>\r\n </ng-container>\r\n\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" [ngClass]=\"{'make-gray': (config.greyOut && config.greyOut(row)) || row.pendingApproval}\"></tr>\r\n </table>\r\n\r\n </div>\r\n\r\n <!-- Changed: Removed *ngIf condition to keep paginator always in DOM and maintain ViewChild reference -->\r\n <!-- Changed: Added CSS class binding to hide when no data instead of conditional rendering -->\r\n <mat-paginator \r\n #tablePaginator \r\n [pageSizeOptions]=\"config.pageSizes ?? [10, 20, 50]\" \r\n [ngClass]=\"{'paginator-hidden': !dataSource || (smallScreen && dataSource?.length === 0)}\"\r\n showFirstLastButtons>\r\n </mat-paginator>\r\n\r\n </div>\r\n \r\n <!-- Capsules -->\r\n <spa-capsules *ngIf=\"config?.viewType === 'capsule'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </spa-capsules>\r\n\r\n\r\n <!-- Cards -->\r\n <spa-cards *ngIf=\"config?.viewType === 'card'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\"\r\n (columnClick)=\"columnClicked($event.column, $event.row)\"\r\n (showBannerEvent)=\"showBanner($event)\">\r\n </spa-cards>\r\n\r\n <!-- Groups - Added: New grouped view type -->\r\n <spa-groups *ngIf=\"config?.viewType === 'grouped'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row, $event.group, $event.button)\">\r\n </spa-groups>\r\n\r\n\r\n <div class=\"tin-center\">\r\n <p *ngIf=\"dataSource?.length == 0\"><em>{{config.noDataMessage ?? 'No Data'}}</em></p>\r\n </div>\r\n\r\n</ng-container>\r\n\r\n\r\n<ng-container *ngIf=\"!hasFormAccess\">\r\n <div class=\"tin-center\">\r\n <p><em>Access Restricted</em></p>\r\n </div>\r\n</ng-container>\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto;max-height:none!important;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input{flex:1;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input>spa-tabs-internal{flex:1;display:flex;flex-direction:column}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i12$2.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i12$2.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i12$2.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i12$2.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i12$2.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i12$2.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i12$2.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i12$2.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i12$2.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i12$2.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i13.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: SearchComponent, selector: "spa-search", inputs: ["config", "smallScreen", "tableDataSource"], outputs: ["searchClick"] }, { kind: "component", type: TableHeaderComponent, selector: "app-table-header", inputs: ["lastSearch", "config", "hideTitle", "tableDataSource", "tileConfig", "smallScreen", "tileReload", "showFilterButton", "data", "tileData"], outputs: ["createClick", "customClick", "refreshClick", "tileClick", "tileUnClick"] }, { kind: "component", type: TableRowComponent, selector: "app-table-row", inputs: ["column", "row", "config", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: TableActionComponent, selector: "app-table-action", inputs: ["displayedButtons", "config", "row", "smallScreen"], outputs: ["actionClick"] }, { kind: "component", type: CapsulesComponent, selector: "spa-capsules", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "component", type: CardsComponent, selector: "spa-cards", inputs: ["config", "dataSource", "displayedButtons", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: GroupsComponent, selector: "spa-groups", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
14105
14465
  }
14106
14466
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TableComponent, decorators: [{
14107
14467
  type: Component,
14108
14468
  args: [{ selector: 'spa-table', standalone: false, template: "\r\n<ng-container *ngIf=\"hasFormAccess\">\r\n\r\n <!-- Search -->\r\n <spa-search\r\n *ngIf=\"config.searchConfig\" [config]=\"config.searchConfig\" [smallScreen]=\"smallScreen\" [tableDataSource]=\"tableDataSource\" style=\"margin-bottom: 20px;\" (searchClick)=\"searchClicked($event)\">\r\n </spa-search>\r\n\r\n <!-- Header -->\r\n <app-table-header\r\n [config]=\"config\" [data]=\"dataSource\" [tableDataSource]=\"tableDataSource\" [tileConfig]=\"config.tileConfig\" [tileData]=\"tileData\" [tileReload]=\"tileReload\" [lastSearch]=\"lastSearch\" [smallScreen]=\"smallScreen\"\r\n [showFilterButton]=\"showFilterButton\" (createClick)=\"newModel()\" (customClick)=\"customModel($event,null)\"\r\n (refreshClick)=\"refreshClicked()\" (tileClick)=\"tileClicked($event)\" (tileUnClick)=\"tileUnClicked($event)\">\r\n </app-table-header>\r\n\r\n\r\n <!-- Table -->\r\n <div *ngIf=\"!config.viewType || config?.viewType === 'table'\">\r\n\r\n <p *ngIf=\"!config\"><em>Configure Table</em></p>\r\n <p *ngIf=\"!dataSource\"><em>Loading...</em></p>\r\n\r\n <div *ngIf=\"dataSource && (!smallScreen || (smallScreen && dataSource?.length > 0))\">\r\n\r\n <table mat-table [dataSource]=\"tableDataSource\" [ngClass]=\"elevation\">\r\n\r\n <ng-container *ngFor=\"let column of config.columns\" [matColumnDef]=\"column.name\">\r\n <th mat-header-cell *matHeaderCellDef >{{ column.alias ?? column.name | camelToWords }}</th>\r\n <td mat-cell *matCellDef=\"let row;\" class=\"right-padding\" >\r\n\r\n <!-- Rows -->\r\n <app-table-row [column]=\"column\" [row]=\"row\" [config]=\"config\" [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked(column.name, row)\" (columnClick)=\"columnClicked(column, row)\" (showBannerEvent)=\"showBanner($event)\">\r\n </app-table-row>\r\n\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"action\">\r\n <th mat-header-cell *matHeaderCellDef> Action </th>\r\n <td mat-cell *matCellDef=\"let row\" [ngStyle]=\"{width:false ? '20px' : actionsWidth}\">\r\n <div class=\"action-buttons-container\">\r\n\r\n <!-- Actions -->\r\n <app-table-action\r\n [displayedButtons]=\"displayedButtons\" [config]=\"config\" [smallScreen]=\"smallScreen\" [row]=\"row\" (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </app-table-action>\r\n\r\n </div>\r\n </td>\r\n </ng-container>\r\n\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" [ngClass]=\"{'make-gray': (config.greyOut && config.greyOut(row)) || row.pendingApproval}\"></tr>\r\n </table>\r\n\r\n </div>\r\n\r\n <!-- Changed: Removed *ngIf condition to keep paginator always in DOM and maintain ViewChild reference -->\r\n <!-- Changed: Added CSS class binding to hide when no data instead of conditional rendering -->\r\n <mat-paginator \r\n #tablePaginator \r\n [pageSizeOptions]=\"config.pageSizes ?? [10, 20, 50]\" \r\n [ngClass]=\"{'paginator-hidden': !dataSource || (smallScreen && dataSource?.length === 0)}\"\r\n showFirstLastButtons>\r\n </mat-paginator>\r\n\r\n </div>\r\n \r\n <!-- Capsules -->\r\n <spa-capsules *ngIf=\"config?.viewType === 'capsule'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </spa-capsules>\r\n\r\n\r\n <!-- Cards -->\r\n <spa-cards *ngIf=\"config?.viewType === 'card'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\"\r\n (columnClick)=\"columnClicked($event.column, $event.row)\"\r\n (showBannerEvent)=\"showBanner($event)\">\r\n </spa-cards>\r\n\r\n <!-- Groups - Added: New grouped view type -->\r\n <spa-groups *ngIf=\"config?.viewType === 'grouped'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row, $event.group, $event.button)\">\r\n </spa-groups>\r\n\r\n\r\n <div class=\"tin-center\">\r\n <p *ngIf=\"dataSource?.length == 0\"><em>{{config.noDataMessage ?? 'No Data'}}</em></p>\r\n </div>\r\n\r\n</ng-container>\r\n\r\n\r\n<ng-container *ngIf=\"!hasFormAccess\">\r\n <div class=\"tin-center\">\r\n <p><em>Access Restricted</em></p>\r\n </div>\r\n</ng-container>\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto;max-height:none!important;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input{flex:1;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input>spa-tabs-internal{flex:1;display:flex;flex-direction:column}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"] }]
14109
- }], ctorParameters: () => [{ type: DataServiceLib }, { type: MessageService }, { type: i1$3.BreakpointObserver }, { type: i1.MatDialog }, { type: ButtonService }, { type: DialogService }, { type: TableConfigService }, { type: ConditionService }, { type: AuthService }], propDecorators: { tablePaginator: [{
14469
+ }], ctorParameters: () => [{ type: DataServiceLib }, { type: MessageService }, { type: i1$3.BreakpointObserver }, { type: i1.MatDialog }, { type: ButtonService }, { type: DialogService }, { type: TableConfigService }, { type: ConditionService }, { type: AuthService }, { type: SignalRService }], propDecorators: { tablePaginator: [{
14110
14470
  type: ViewChild,
14111
14471
  args: ['tablePaginator', { static: false }]
14112
14472
  }], data: [{
@@ -16221,6 +16581,108 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
16221
16581
  type: Input
16222
16582
  }] } });
16223
16583
 
16584
+ class SpaLandingComponent {
16585
+ constructor(router, authService, el, sanitizer // Changed: inject sanitizer for safe SVG rendering
16586
+ ) {
16587
+ this.router = router;
16588
+ this.authService = authService;
16589
+ this.el = el;
16590
+ this.sanitizer = sanitizer;
16591
+ this.mobileMenuOpen = false;
16592
+ this.navScrolled = false;
16593
+ this.currentYear = new Date().getFullYear(); // Used in footer copyright
16594
+ this.observer = null;
16595
+ }
16596
+ // Sanitizes the navBrand SVG string so Angular allows [innerHTML] binding (developer config, not user input)
16597
+ // Changed: inject width/height before sanitising — CSS ::ng-deep cannot reach innerHTML content in library builds
16598
+ get safeNavBrandSvg() {
16599
+ if (!this.config.navBrand?.svgMarkup)
16600
+ return null;
16601
+ const sized = this.config.navBrand.svgMarkup.replace(/^<svg/, '<svg width="36" height="36"');
16602
+ return this.sanitizer.bypassSecurityTrustHtml(sized);
16603
+ }
16604
+ // Sanitizes the hero watermark SVG string
16605
+ get safeHeroWatermarkSvg() {
16606
+ return this.config.hero.watermarkSvg
16607
+ ? this.sanitizer.bypassSecurityTrustHtml(this.config.hero.watermarkSvg)
16608
+ : null;
16609
+ }
16610
+ // Sanitizes the footer brand SVG string
16611
+ // Changed: inject width/height before sanitising — same encapsulation fix as navBrand
16612
+ get safeFooterBrandSvg() {
16613
+ if (!this.config.footerBrand?.svgMarkup)
16614
+ return null;
16615
+ const sized = this.config.footerBrand.svgMarkup.replace(/^<svg/, '<svg width="36" height="36"');
16616
+ return this.sanitizer.bypassSecurityTrustHtml(sized);
16617
+ }
16618
+ ngOnInit() {
16619
+ // Apply brand colors as CSS variables on the host element so all child rules inherit them
16620
+ this.applyColors();
16621
+ // Restore session if possible and redirect to home
16622
+ this.authService.tryRestoreSession().then(success => {
16623
+ if (success)
16624
+ this.router.navigate(['home']);
16625
+ });
16626
+ }
16627
+ ngAfterViewInit() {
16628
+ // Scroll-triggered fade-in for sections with lp-animate class
16629
+ this.observer = new IntersectionObserver((entries) => {
16630
+ entries.forEach(entry => {
16631
+ if (entry.isIntersecting)
16632
+ entry.target.classList.add('lp-visible');
16633
+ });
16634
+ }, { threshold: 0.1 });
16635
+ // Observe only within this component's DOM to avoid affecting other pages
16636
+ this.el.nativeElement.querySelectorAll('.lp-animate').forEach((el) => {
16637
+ this.observer?.observe(el);
16638
+ });
16639
+ }
16640
+ ngOnDestroy() {
16641
+ this.observer?.disconnect();
16642
+ }
16643
+ onWindowScroll() {
16644
+ // Add frosted-glass background to navbar after scrolling past hero
16645
+ this.navScrolled = window.scrollY > 50;
16646
+ }
16647
+ scrollToSection(target) {
16648
+ this.mobileMenuOpen = false; // Close mobile menu on nav click
16649
+ const element = document.getElementById(target);
16650
+ element?.scrollIntoView({ behavior: 'smooth' });
16651
+ }
16652
+ toggleMobileMenu() {
16653
+ this.mobileMenuOpen = !this.mobileMenuOpen;
16654
+ }
16655
+ navigateTo(path) {
16656
+ this.router.navigate([path]);
16657
+ this.mobileMenuOpen = false;
16658
+ }
16659
+ // Returns light tinted background for feature icon (hex + '20' = ~12% opacity)
16660
+ iconBg(hex) {
16661
+ return hex + '20';
16662
+ }
16663
+ // Sets CSS custom properties on host element so child rules use app-specific brand colors
16664
+ applyColors() {
16665
+ const s = this.el.nativeElement.style;
16666
+ const c = this.config.colors;
16667
+ s.setProperty('--lp-primary', c.primary);
16668
+ s.setProperty('--lp-primary-dark', c.primaryDark);
16669
+ s.setProperty('--lp-primary-light', c.primaryLight);
16670
+ s.setProperty('--lp-secondary', c.secondary);
16671
+ s.setProperty('--lp-secondary-light', c.secondaryLight);
16672
+ }
16673
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SpaLandingComponent, deps: [{ token: i1$2.Router }, { token: AuthService }, { token: i0.ElementRef }, { token: i1$5.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component }); }
16674
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: SpaLandingComponent, isStandalone: false, selector: "spa-landing", inputs: { config: "config" }, host: { listeners: { "window:scroll": "onWindowScroll()" } }, ngImport: i0, template: "<!-- ===== NAVBAR ===== -->\n<nav class=\"lp-navbar\" [class.lp-navbar-scrolled]=\"navScrolled\">\n <div class=\"lp-container lp-nav-inner\">\n <!-- Brand: custom SVG override OR default img+name -->\n <div class=\"lp-nav-brand\" (click)=\"scrollToSection('lp-hero')\">\n <ng-container *ngIf=\"config.navBrand; else defaultNavBrand\">\n <span class=\"lp-nav-brand-svg\" [innerHTML]=\"safeNavBrandSvg\"></span>\n <div *ngIf=\"config.navBrand.wordmarkLine1\" class=\"lp-nav-brand-wordmark\">\n <span class=\"lp-nav-wordmark-line1\">{{ config.navBrand.wordmarkLine1 }}</span>\n <span *ngIf=\"config.navBrand.wordmarkLine2\" class=\"lp-nav-wordmark-line2\">{{ config.navBrand.wordmarkLine2 }}</span>\n </div>\n </ng-container>\n <ng-template #defaultNavBrand>\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-nav-logo\">\n <span class=\"lp-nav-brand-text\">{{ config.appName }}</span>\n </ng-template>\n </div>\n <!-- Desktop nav links -->\n <div class=\"lp-nav-links\">\n <a *ngFor=\"let link of config.navLinks\" (click)=\"scrollToSection(link.target)\">{{ link.label }}</a>\n </div>\n <!-- Mobile menu overlay -->\n <div class=\"lp-mobile-menu\" [class.lp-mobile-menu-open]=\"mobileMenuOpen\">\n <a *ngFor=\"let link of config.navLinks\" (click)=\"scrollToSection(link.target)\">{{ link.label }}</a>\n <div class=\"lp-mobile-auth\">\n <a class=\"lp-nav-login\" (click)=\"navigateTo('login')\">Log in</a>\n <button class=\"lp-btn lp-btn-primary\" (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">Get Started</button>\n </div>\n </div>\n <!-- Desktop auth -->\n <div class=\"lp-nav-auth\">\n <a class=\"lp-nav-login\" (click)=\"navigateTo('login')\">Log in</a>\n <button class=\"lp-btn lp-btn-primary lp-btn-sm\" (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">Get Started</button>\n </div>\n <!-- Hamburger for mobile -->\n <button class=\"lp-hamburger\" (click)=\"toggleMobileMenu()\" [class.lp-hamburger-open]=\"mobileMenuOpen\">\n <span></span><span></span><span></span>\n </button>\n </div>\n</nav>\n\n<!-- ===== HERO ===== -->\n<section id=\"lp-hero\" class=\"lp-hero-bg\" [class.lp-hero-split]=\"config.hero.layout === 'split'\">\n <!-- Optional faded watermark SVG (e.g. company logo) -->\n <!-- Changed: wrapper div gets Angular scoping attribute; inner div holds innerHTML so svg size CSS applies to scoped .lp-hero-watermark-inner -->\n <div *ngIf=\"config.hero.watermarkSvg\" class=\"lp-hero-watermark\" aria-hidden=\"true\">\n <div class=\"lp-hero-watermark-inner\" [innerHTML]=\"safeHeroWatermarkSvg\"></div>\n </div>\n\n <div class=\"lp-container lp-hero-content\">\n <!-- Left / centered column: brand, headline, CTAs -->\n <div class=\"lp-hero-main\">\n <!-- App brand: logo + name + decorative divider -->\n <div class=\"lp-hero-brand\">\n <div class=\"lp-hero-brand-logo\">\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-hero-brand-icon\">\n <span class=\"lp-hero-brand-name\">{{ config.appName }}</span>\n </div>\n <div class=\"lp-hero-divider\" aria-hidden=\"true\">\n <span class=\"lp-hero-divider-line\"></span>\n <span class=\"lp-hero-divider-dot\"></span>\n <span class=\"lp-hero-divider-line\"></span>\n </div>\n </div>\n <!-- Hero text: headline, subtitle, CTAs, trust signals -->\n <div class=\"lp-hero-text\">\n <div *ngIf=\"config.hero.badge\" class=\"lp-hero-badge\">{{ config.hero.badge }}</div>\n <h1>{{ config.hero.headline }} <span class=\"lp-gradient-text\">{{ config.hero.gradientText }}</span></h1>\n <p class=\"lp-hero-subtitle\">{{ config.hero.subtitle }}</p>\n <div class=\"lp-hero-ctas\">\n <button class=\"lp-btn lp-btn-primary lp-btn-lg\" (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">\n {{ config.hero.primaryCTA }}\n </button>\n <button class=\"lp-btn lp-btn-outline lp-btn-lg\" (click)=\"scrollToSection(config.hero.secondaryCTASection)\">\n {{ config.hero.secondaryCTA }}\n </button>\n </div>\n <!-- Trust signals with checkmark SVG icons -->\n <div class=\"lp-trust-signals\">\n <div *ngFor=\"let signal of config.hero.trustSignals\" class=\"lp-trust-item\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path d=\"M20 6L9 17l-5-5\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n <span>{{ signal }}</span>\n </div>\n </div>\n </div>\n </div>\n <!-- App visual: always rendered when projected content exists (stacked below hero text) -->\n <!-- Changed: removed *ngIf so visual always renders below text; layout is always stacked/column -->\n <div class=\"lp-hero-visual\">\n <ng-content select=\"[lpHeroVisual]\"></ng-content>\n </div>\n </div>\n</section>\n\n<!-- ===== METRICS (optional) ===== -->\n<section *ngIf=\"config.metrics\" class=\"lp-section lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-metrics-grid\">\n <div *ngFor=\"let metric of config.metrics\" class=\"lp-metric-card\">\n <div class=\"lp-metric-value\">{{ metric.value }}</div>\n <div class=\"lp-metric-label\">{{ metric.label }}</div>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== FEATURES ===== -->\n<section id=\"lp-features\" class=\"lp-section lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.features.title }}</h2>\n <p>{{ config.features.subtitle }}</p>\n </div>\n <div class=\"lp-features-grid\">\n <div *ngFor=\"let feature of config.features.items\" class=\"lp-feature-card\">\n <!-- Icon tint: hex + '20' = ~12% opacity background -->\n <div class=\"lp-feature-icon\"\n [style.background]=\"iconBg(feature.color)\"\n [style.color]=\"feature.color\">\n <span class=\"material-icons\">{{ feature.icon }}</span>\n </div>\n <h3 class=\"lp-feature-title\">{{ feature.title }}</h3>\n <p class=\"lp-feature-desc\">{{ feature.description }}</p>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== WORKFLOW (optional) ===== -->\n<section *ngIf=\"config.workflow\" id=\"lp-workflow\" class=\"lp-section lp-section-alt lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.workflow!.title }}</h2>\n <p *ngIf=\"config.workflow!.subtitle\">{{ config.workflow!.subtitle }}</p>\n </div>\n\n <!-- Road metaphor layout: horizontal road line with waypoints -->\n <div *ngIf=\"config.workflow!.style === 'road'\" class=\"lp-workflow-road\">\n <div class=\"lp-road-line\"></div>\n <div *ngFor=\"let step of config.workflow!.steps\" class=\"lp-waypoint\">\n <div class=\"lp-waypoint-marker\">\n <span class=\"material-icons\">{{ step.icon }}</span>\n </div>\n <div class=\"lp-waypoint-number\">Step {{ step.number }}</div>\n <div class=\"lp-waypoint-title\">{{ step.title }}</div>\n <div class=\"lp-waypoint-desc\">{{ step.description }}</div>\n </div>\n </div>\n\n <!-- Default steps grid layout -->\n <div *ngIf=\"!config.workflow!.style || config.workflow!.style === 'steps'\" class=\"lp-steps-grid\">\n <div *ngFor=\"let step of config.workflow!.steps; let i = index\" class=\"lp-step-card\">\n <div class=\"lp-step-number\">{{ step.number }}</div>\n <div class=\"lp-step-icon\">\n <span class=\"material-icons\">{{ step.icon }}</span>\n </div>\n <h3 class=\"lp-step-title\">{{ step.title }}</h3>\n <p class=\"lp-step-desc\">{{ step.description }}</p>\n <!-- Connector line between steps, not after last -->\n <div *ngIf=\"i < config.workflow!.steps.length - 1\" class=\"lp-step-connector\"></div>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== CUSTOM SECTION SLOT ===== -->\n<!-- Consumer projects app-specific sections here (fleet board, benefits rows, etc.)\n using [lpCustomSection] attribute on a wrapper element -->\n<ng-content select=\"[lpCustomSection]\"></ng-content>\n\n<!-- ===== MODULES (optional) ===== -->\n<section *ngIf=\"config.modules\" id=\"lp-modules\" class=\"lp-section lp-section-alt lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.modules!.title }}</h2>\n <p>{{ config.modules!.subtitle }}</p>\n </div>\n <div class=\"lp-modules-grid\">\n <div *ngFor=\"let group of config.modules!.groups\" class=\"lp-module-group\">\n <div class=\"lp-module-group-label\">{{ group.category }}</div>\n <div class=\"lp-module-tiles\">\n <div *ngFor=\"let tile of group.tiles\" class=\"lp-module-tile\">\n <span class=\"material-icons\">{{ tile.icon }}</span>\n <span>{{ tile.name }}</span>\n </div>\n </div>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== SECURITY (optional \u2014 dark background) ===== -->\n<section *ngIf=\"config.security\" class=\"lp-section lp-section-dark lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header lp-header-light\">\n <h2>{{ config.security!.title }}</h2>\n <p>{{ config.security!.subtitle }}</p>\n </div>\n <div class=\"lp-security-grid\">\n <div *ngFor=\"let item of config.security!.items\" class=\"lp-security-card\">\n <div class=\"lp-security-icon\">\n <span class=\"material-icons\">{{ item.icon }}</span>\n </div>\n <h3>{{ item.title }}</h3>\n <p>{{ item.description }}</p>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== TESTIMONIALS (optional) ===== -->\n<section *ngIf=\"config.testimonials\" class=\"lp-section lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.testimonials!.title }}</h2>\n <p *ngIf=\"config.testimonials!.subtitle\">{{ config.testimonials!.subtitle }}</p>\n </div>\n <div class=\"lp-testimonials-grid\">\n <div *ngFor=\"let item of config.testimonials!.items\" class=\"lp-testimonial-card\">\n <div class=\"lp-stars\">\n <span class=\"material-icons\">star</span>\n <span class=\"material-icons\">star</span>\n <span class=\"material-icons\">star</span>\n <span class=\"material-icons\">star</span>\n <span class=\"material-icons\">star</span>\n </div>\n <p class=\"lp-testimonial-quote\">\"{{ item.quote }}\"</p>\n <div class=\"lp-testimonial-author\">\n <div class=\"lp-author-avatar\">{{ item.authorInitials }}</div>\n <div>\n <div class=\"lp-author-name\">{{ item.authorName }}</div>\n <div class=\"lp-author-role\">{{ item.authorRole }}</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== PRICING (optional) ===== -->\n<section *ngIf=\"config.pricing\" id=\"lp-pricing\" class=\"lp-section lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.pricing!.title }}</h2>\n <p *ngIf=\"config.pricing!.subtitle\">{{ config.pricing!.subtitle }}</p>\n </div>\n <div class=\"lp-pricing-grid\">\n <div *ngFor=\"let plan of config.pricing!.plans\"\n class=\"lp-pricing-card\"\n [class.lp-pricing-popular]=\"plan.popular\">\n <div *ngIf=\"plan.popular\" class=\"lp-popular-badge\">Most Popular</div>\n <div class=\"lp-pricing-header\">\n <h3>{{ plan.name }}</h3>\n <div class=\"lp-pricing-price\">\n <span class=\"lp-price-amount\">{{ plan.price }}</span>\n <span *ngIf=\"plan.period\" class=\"lp-price-period\">{{ plan.period }}</span>\n </div>\n <p class=\"lp-pricing-desc\">{{ plan.description }}</p>\n </div>\n <ul class=\"lp-pricing-features\">\n <li *ngFor=\"let feature of plan.features\">{{ feature }}</li>\n </ul>\n <!-- Popular plans get primary button, others get outline -->\n <button *ngIf=\"plan.popular\"\n class=\"lp-btn lp-btn-primary lp-btn-block\"\n (click)=\"plan.ctaRoute ? navigateTo(plan.ctaRoute) : scrollToSection(plan.ctaSection || '')\">\n {{ plan.ctaLabel }}\n </button>\n <button *ngIf=\"!plan.popular\"\n class=\"lp-btn lp-btn-outline lp-btn-block\"\n (click)=\"plan.ctaRoute ? navigateTo(plan.ctaRoute) : scrollToSection(plan.ctaSection || '')\">\n {{ plan.ctaLabel }}\n </button>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== FINAL CTA BANNER (optional) ===== -->\n<section *ngIf=\"config.finalCta\" class=\"lp-final-cta lp-animate\">\n <div class=\"lp-container lp-cta-content\">\n <h2>{{ config.finalCta!.headline }}</h2>\n <p>{{ config.finalCta!.subtitle }}</p>\n <div class=\"lp-cta-buttons\">\n <button class=\"lp-btn lp-btn-white lp-btn-lg\"\n (click)=\"navigateTo(config.finalCta!.primaryCTARoute || 'signup')\">\n {{ config.finalCta!.primaryCTA }}\n </button>\n <button *ngIf=\"config.finalCta!.secondaryCTA\"\n class=\"lp-btn lp-btn-outline-white lp-btn-lg\"\n (click)=\"scrollToSection(config.finalCta!.secondaryCTASection || '')\">\n {{ config.finalCta!.secondaryCTA }}\n </button>\n </div>\n </div>\n</section>\n\n<!-- ===== FOOTER ===== -->\n<footer class=\"lp-footer\" id=\"lp-footer\">\n <div class=\"lp-container\">\n\n <!-- Rich multi-column footer when footerColumns provided -->\n <div *ngIf=\"config.footerColumns && config.footerColumns.length; else simpleFooter\" class=\"lp-footer-grid\">\n <!-- Brand column -->\n <div class=\"lp-footer-brand-col\">\n <!-- Custom SVG footer brand OR default img+name -->\n <ng-container *ngIf=\"config.footerBrand; else defaultFooterBrand\">\n <div class=\"lp-footer-brand-custom\">\n <span class=\"lp-footer-brand-svg\" [innerHTML]=\"safeFooterBrandSvg\"></span>\n <div *ngIf=\"config.footerBrand.wordmarkLine1\" class=\"lp-footer-brand-wordmark\">\n <span class=\"lp-footer-wordmark-line1\">{{ config.footerBrand.wordmarkLine1 }}</span>\n <span *ngIf=\"config.footerBrand.wordmarkLine2\" class=\"lp-footer-wordmark-line2\">{{ config.footerBrand.wordmarkLine2 }}</span>\n </div>\n </div>\n </ng-container>\n <ng-template #defaultFooterBrand>\n <div class=\"lp-footer-brand-default\">\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-footer-logo\">\n <span class=\"lp-footer-name\">{{ config.appName }}</span>\n </div>\n </ng-template>\n <p *ngIf=\"config.footerTagline\" class=\"lp-footer-tagline\">{{ config.footerTagline }}</p>\n </div>\n <!-- Link columns -->\n <div *ngFor=\"let col of config.footerColumns\" class=\"lp-footer-col\">\n <h4>{{ col.title }}</h4>\n <a *ngFor=\"let link of col.links\"\n (click)=\"link.section ? scrollToSection(link.section) : (link.route ? navigateTo(link.route) : null)\">\n {{ link.label }}\n </a>\n </div>\n </div>\n\n <!-- Simple single-row footer (default) -->\n <ng-template #simpleFooter>\n <div class=\"lp-footer-inner\">\n <div class=\"lp-footer-brand\">\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-footer-logo\">\n <span class=\"lp-footer-name\">{{ config.appName }}</span>\n </div>\n <div class=\"lp-footer-links\">\n <a (click)=\"navigateTo('login')\">Log in</a>\n <a (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">Sign up</a>\n </div>\n <div class=\"lp-footer-copy\">\n &copy; {{ currentYear }} {{ config.footerCompany || 'alsquare technologies' }}. All rights reserved.\n </div>\n </div>\n </ng-template>\n\n <!-- Footer bottom bar (shown for both layouts) -->\n <div *ngIf=\"config.footerColumns && config.footerColumns.length\" class=\"lp-footer-bottom\">\n <span>&copy; {{ currentYear }} {{ config.footerCompany || 'alsquare technologies' }}. All rights reserved.</span>\n </div>\n </div>\n</footer>\n", styles: ["@import\"https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800;900&display=swap\";:host{--lp-primary: #1E3A5F;--lp-primary-dark: #152C4A;--lp-primary-light: #EBF0F7;--lp-secondary: #F59E0B;--lp-secondary-light:#FEF3C7;--lp-dark: #0D1117;--lp-gray-800: #1F2937;--lp-gray-700: #374151;--lp-gray-600: #4B5563;--lp-gray-400: #9CA3AF;--lp-gray-200: #E5E7EB;--lp-gray-100: #F3F4F6;--lp-gray-50: #F9FAFB;--lp-white: #FFFFFF;--lp-radius: 12px;--lp-radius-sm: 8px;--lp-radius-lg: 20px;--lp-radius-xl: 28px;--lp-shadow-xs: 0 1px 2px rgba(0,0,0,.05);--lp-shadow: 0 1px 3px rgba(0,0,0,.07), 0 1px 2px rgba(0,0,0,.05);--lp-shadow-md: 0 4px 8px rgba(0,0,0,.06), 0 2px 4px rgba(0,0,0,.04);--lp-shadow-lg: 0 12px 28px rgba(0,0,0,.1), 0 4px 8px rgba(0,0,0,.06);--lp-shadow-xl: 0 20px 40px rgba(0,0,0,.12), 0 8px 16px rgba(0,0,0,.07);--lp-ease: cubic-bezier(.4, 0, .2, 1);--lp-spring: cubic-bezier(.34, 1.56, .64, 1);display:block;font-family:Outfit,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;color:var(--lp-gray-700);line-height:1.6;overflow-x:hidden;-webkit-font-smoothing:antialiased}.lp-container{max-width:1200px;margin:0 auto;padding:0 32px}.lp-navbar{position:fixed;top:0;left:0;right:0;z-index:1000;padding:18px 0;transition:background .3s var(--lp-ease),padding .3s var(--lp-ease),box-shadow .3s var(--lp-ease)}.lp-navbar-scrolled{background:#ffffffeb;backdrop-filter:blur(16px) saturate(180%);-webkit-backdrop-filter:blur(16px) saturate(180%);box-shadow:0 1px #0000000f,0 2px 8px #0000000a;padding:12px 0}.lp-nav-inner{display:flex;align-items:center;justify-content:space-between}.lp-nav-brand{display:flex;align-items:center;gap:10px;cursor:pointer;text-decoration:none}.lp-nav-logo{width:30px;height:30px;border-radius:7px;object-fit:contain}.lp-nav-brand-text{font-size:18px;font-weight:700;color:var(--lp-primary);letter-spacing:-.01em}.lp-nav-links{display:flex;align-items:center;gap:36px}.lp-nav-links a{font-size:14px;font-weight:500;color:var(--lp-gray-600);cursor:pointer;transition:color .2s;text-decoration:none;letter-spacing:.01em}.lp-nav-links a:hover{color:var(--lp-primary)}.lp-nav-auth{display:flex;align-items:center;gap:12px}.lp-nav-login{font-size:14px;font-weight:500;color:var(--lp-gray-600);cursor:pointer;transition:color .2s;text-decoration:none}.lp-nav-login:hover{color:var(--lp-primary)}.lp-hamburger{display:none;flex-direction:column;gap:5px;background:none;border:none;cursor:pointer;padding:4px;z-index:1001}.lp-hamburger span{display:block;width:22px;height:2px;background:var(--lp-gray-700);border-radius:2px;transition:transform .3s var(--lp-ease),opacity .3s}.lp-hamburger-open span:nth-child(1){transform:translateY(7px) rotate(45deg)}.lp-hamburger-open span:nth-child(2){opacity:0}.lp-hamburger-open span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}.lp-mobile-menu{display:none}.lp-mobile-auth{display:flex;flex-direction:column;gap:12px;margin-top:24px;padding-top:24px;border-top:1px solid var(--lp-gray-200)}.lp-btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;font-family:Outfit,sans-serif;font-size:14px;font-weight:600;padding:10px 22px;border-radius:var(--lp-radius-sm);border:none;cursor:pointer;transition:all .25s var(--lp-ease);text-decoration:none;white-space:nowrap;letter-spacing:.01em}.lp-btn-primary{background:var(--lp-primary);color:var(--lp-white);box-shadow:0 1px 3px #00000026,inset 0 1px #ffffff1f}.lp-btn-primary:hover{background:var(--lp-primary-dark);transform:translateY(-1px);box-shadow:0 6px 16px #0003,inset 0 1px #ffffff1f}.lp-btn-primary:active{transform:translateY(0)}.lp-btn-outline{background:transparent;color:var(--lp-primary);border:1.5px solid var(--lp-primary)}.lp-btn-outline:hover{background:var(--lp-primary);color:var(--lp-white);transform:translateY(-1px);box-shadow:0 4px 12px #00000026}.lp-btn-sm{padding:7px 16px;font-size:13px}.lp-btn-lg{padding:14px 32px;font-size:15px;border-radius:var(--lp-radius)}.lp-btn-block{width:100%}.lp-hero-bg{position:relative;overflow:hidden;padding:140px 0 100px;background:radial-gradient(ellipse 80% 60% at 60% -10%,color-mix(in srgb,var(--lp-primary) 8%,transparent) 0%,transparent 70%),radial-gradient(ellipse 60% 50% at -10% 80%,color-mix(in srgb,var(--lp-secondary) 6%,transparent) 0%,transparent 70%),linear-gradient(160deg,var(--lp-primary-light) 0%,#FAFBFF 45%,var(--lp-secondary-light) 100%);min-height:100vh;display:flex;align-items:center}.lp-hero-bg:before{content:\"\";position:absolute;inset:0;background-image:radial-gradient(circle,rgba(0,0,0,.04) 1px,transparent 1px);background-size:32px 32px;pointer-events:none;-webkit-mask-image:radial-gradient(ellipse 80% 80% at 50% 50%,black 40%,transparent 100%);mask-image:radial-gradient(ellipse 80% 80% at 50% 50%,black 40%,transparent 100%)}.lp-hero-bg:after{content:\"\";position:absolute;width:700px;height:700px;border-radius:50%;background:color-mix(in srgb,var(--lp-primary) 5%,transparent);top:-200px;right:-150px;filter:blur(100px);pointer-events:none}.lp-hero-content{position:relative;z-index:1;text-align:center;width:100%}.lp-hero-badge{display:inline-flex;align-items:center;gap:6px;background:color-mix(in srgb,var(--lp-primary) 10%,transparent);color:var(--lp-primary);border:1px solid color-mix(in srgb,var(--lp-primary) 20%,transparent);border-radius:100px;padding:6px 16px;font-size:13px;font-weight:600;margin-bottom:20px;letter-spacing:.02em}.lp-hero-brand{margin-bottom:20px;text-align:center}.lp-hero-brand-logo{display:inline-flex;align-items:center;gap:14px;margin-bottom:12px}.lp-hero-brand-icon{height:52px;width:52px;border-radius:14px;object-fit:contain;box-shadow:0 4px 12px #0000001f}.lp-hero-brand-name{font-size:38px;font-weight:800;color:var(--lp-primary);letter-spacing:-.03em;font-family:Outfit,sans-serif}.lp-hero-divider{display:flex;align-items:center;justify-content:center;gap:10px}.lp-hero-divider-line{display:block;height:1px;width:40px;background:linear-gradient(90deg,transparent,var(--lp-primary),transparent);opacity:.3}.lp-hero-divider-dot{display:block;height:5px;width:5px;border-radius:50%;background:var(--lp-secondary);opacity:.8}.lp-hero-text{max-width:760px;margin:0 auto 64px}.lp-hero-text h1{font-family:Outfit,sans-serif;font-size:68px;font-weight:900;line-height:1.05;color:var(--lp-dark);margin:0 0 24px;letter-spacing:-.03em}.lp-gradient-text{background:linear-gradient(135deg,var(--lp-primary) 0%,var(--lp-secondary) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}.lp-hero-subtitle{font-size:19px;color:var(--lp-gray-600);line-height:1.65;font-weight:400;max-width:560px;margin:0 auto 36px}.lp-hero-ctas{display:flex;align-items:center;justify-content:center;gap:14px;margin-bottom:32px;flex-wrap:wrap}.lp-trust-signals{display:flex;align-items:center;justify-content:center;gap:28px;flex-wrap:wrap}.lp-trust-item{display:flex;align-items:center;gap:7px;font-size:13px;font-weight:500;color:var(--lp-gray-500, #6B7280)}.lp-trust-item svg{color:var(--lp-primary);flex-shrink:0}.lp-section{padding:100px 0;background:var(--lp-white)}.lp-section-alt{background:var(--lp-gray-50)}.lp-section-header{text-align:center;margin-bottom:64px}.lp-section-header h2{font-family:Outfit,sans-serif;font-size:42px;font-weight:800;color:var(--lp-dark);margin:0 0 14px;letter-spacing:-.025em;line-height:1.15}.lp-section-header p{font-size:17px;color:var(--lp-gray-600);max-width:540px;margin:0 auto;line-height:1.65}.lp-features-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:20px}.lp-feature-card{background:var(--lp-white);border-radius:var(--lp-radius-lg);padding:36px 28px;transition:transform .25s var(--lp-ease),box-shadow .25s var(--lp-ease);border:1px solid var(--lp-gray-200);box-shadow:var(--lp-shadow-xs);position:relative;overflow:hidden}.lp-feature-card:before{content:\"\";position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,var(--lp-primary),var(--lp-secondary));transform:scaleX(0);transform-origin:left;transition:transform .3s var(--lp-ease);border-radius:3px 3px 0 0}.lp-feature-card:hover{transform:translateY(-5px);box-shadow:var(--lp-shadow-xl);border-color:transparent}.lp-feature-card:hover:before{transform:scaleX(1)}.lp-feature-icon{width:52px;height:52px;border-radius:var(--lp-radius-sm);display:flex;align-items:center;justify-content:center;margin-bottom:20px}.lp-feature-icon .material-icons{font-size:26px}.lp-feature-title{font-family:Outfit,sans-serif;font-size:17px;font-weight:700;color:var(--lp-dark);margin:0 0 10px;letter-spacing:-.01em}.lp-feature-desc{font-size:14px;color:var(--lp-gray-600);line-height:1.65;margin:0}.lp-steps-grid{display:flex;align-items:flex-start;justify-content:space-between;position:relative;gap:16px}.lp-step-card{flex:1;display:flex;flex-direction:column;align-items:center;text-align:center;position:relative;padding:0 8px}.lp-step-number{font-size:11px;font-weight:700;color:var(--lp-secondary);text-transform:uppercase;letter-spacing:2px;margin-bottom:10px}.lp-step-icon{width:56px;height:56px;border-radius:50%;background:var(--lp-primary);display:flex;align-items:center;justify-content:center;margin-bottom:18px;box-shadow:0 4px 14px color-mix(in srgb,var(--lp-primary) 30%,transparent);border:3px solid var(--lp-white);position:relative;z-index:1}.lp-step-icon .material-icons{font-size:24px;color:var(--lp-white)}.lp-step-title{font-family:Outfit,sans-serif;font-size:15px;font-weight:700;color:var(--lp-dark);margin:0 0 8px;letter-spacing:-.01em}.lp-step-desc{font-size:13px;color:var(--lp-gray-600);line-height:1.55;margin:0}.lp-step-connector{position:absolute;top:40px;left:60%;width:80%;height:2px;background:repeating-linear-gradient(90deg,color-mix(in srgb,var(--lp-secondary) 50%,transparent) 0px,color-mix(in srgb,var(--lp-secondary) 50%,transparent) 10px,transparent 10px,transparent 18px);pointer-events:none;z-index:0}.lp-metrics-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:0;border:1px solid var(--lp-gray-200);border-radius:var(--lp-radius-lg);overflow:hidden;background:var(--lp-white);box-shadow:var(--lp-shadow)}.lp-metric-card{text-align:center;padding:40px 24px;border-right:1px solid var(--lp-gray-200);transition:background .2s}.lp-metric-card:last-child{border-right:none}.lp-metric-card:hover{background:var(--lp-gray-50)}.lp-metric-value{font-family:Outfit,sans-serif;font-size:48px;font-weight:900;color:var(--lp-primary);line-height:1;letter-spacing:-.03em;margin-bottom:8px}.lp-metric-label{font-size:14px;font-weight:500;color:var(--lp-gray-600)}.lp-modules-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:20px}.lp-module-group{background:var(--lp-white);border-radius:var(--lp-radius-lg);padding:28px 28px 24px;border:1px solid var(--lp-gray-200);box-shadow:var(--lp-shadow-xs);transition:box-shadow .2s}.lp-module-group:hover{box-shadow:var(--lp-shadow-md)}.lp-module-group-label{font-family:Outfit,sans-serif;font-size:11px;font-weight:700;color:var(--lp-primary);margin-bottom:16px;text-transform:uppercase;letter-spacing:1px}.lp-module-tiles{display:flex;flex-wrap:wrap;gap:8px}.lp-module-tile{display:inline-flex;align-items:center;gap:6px;background:var(--lp-gray-50);border:1px solid var(--lp-gray-200);padding:7px 13px;border-radius:100px;font-size:12px;font-weight:600;color:var(--lp-gray-700);transition:all .2s var(--lp-ease);cursor:default}.lp-module-tile:hover{background:color-mix(in srgb,var(--lp-primary) 6%,transparent);border-color:color-mix(in srgb,var(--lp-primary) 25%,transparent);color:var(--lp-primary);transform:translateY(-1px)}.lp-module-tile .material-icons{font-size:15px;color:var(--lp-primary)}.lp-footer{background:var(--lp-dark);padding:44px 0 36px;border-top:1px solid rgba(255,255,255,.04)}.lp-footer-inner{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:20px}.lp-footer-brand{display:flex;align-items:center;gap:10px}.lp-footer-logo{width:30px;height:30px;border-radius:7px;object-fit:contain;opacity:.9}.lp-footer-name{font-family:Outfit,sans-serif;font-size:17px;font-weight:700;color:var(--lp-white);letter-spacing:-.01em}.lp-footer-links{display:flex;gap:28px}.lp-footer-links a{font-size:14px;font-weight:500;color:#ffffff80;cursor:pointer;text-decoration:none;transition:color .2s}.lp-footer-links a:hover{color:var(--lp-white)}.lp-footer-copy{font-size:13px;color:#ffffff59;width:100%;text-align:center;padding-top:20px;margin-top:4px;border-top:1px solid rgba(255,255,255,.06)}.lp-animate{opacity:0;transform:translateY(28px);transition:opacity .65s var(--lp-ease),transform .65s var(--lp-ease)}.lp-visible{opacity:1;transform:translateY(0)}.lp-visible .lp-feature-card:nth-child(1){transition-delay:0ms}.lp-visible .lp-feature-card:nth-child(2){transition-delay:60ms}.lp-visible .lp-feature-card:nth-child(3){transition-delay:.12s}.lp-visible .lp-feature-card:nth-child(4){transition-delay:.18s}.lp-visible .lp-feature-card:nth-child(5){transition-delay:.24s}.lp-visible .lp-feature-card:nth-child(6){transition-delay:.3s}.lp-visible .lp-step-card:nth-child(1){transition-delay:0ms}.lp-visible .lp-step-card:nth-child(2){transition-delay:80ms}.lp-visible .lp-step-card:nth-child(3){transition-delay:.16s}.lp-visible .lp-step-card:nth-child(4){transition-delay:.24s}.lp-visible .lp-metric-card:nth-child(1){transition-delay:0ms}.lp-visible .lp-metric-card:nth-child(2){transition-delay:60ms}.lp-visible .lp-metric-card:nth-child(3){transition-delay:.12s}.lp-visible .lp-metric-card:nth-child(4){transition-delay:.18s}@media (max-width: 1024px){.lp-hero-text h1{font-size:52px}.lp-section-header h2{font-size:36px}.lp-features-grid,.lp-metrics-grid{grid-template-columns:repeat(2,1fr)}.lp-metrics-grid .lp-metric-card:nth-child(2){border-right:none}.lp-metrics-grid .lp-metric-card:nth-child(3){border-top:1px solid var(--lp-gray-200)}.lp-metrics-grid .lp-metric-card:nth-child(4){border-top:1px solid var(--lp-gray-200)}.lp-steps-grid{flex-wrap:wrap;justify-content:center;gap:40px}.lp-step-connector{display:none}}@media (max-width: 767px){.lp-container{padding:0 20px}.lp-nav-links{display:none}.lp-hamburger{display:flex}.lp-mobile-auth{display:none}.lp-mobile-menu{display:none;position:fixed;inset:0;background:#fffffff7;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);flex-direction:column;align-items:center;justify-content:center;gap:28px;z-index:999}.lp-mobile-menu-open{display:flex}.lp-mobile-menu a{font-size:22px;font-weight:700;color:var(--lp-dark);cursor:pointer;text-decoration:none;letter-spacing:-.01em}.lp-hero-bg{padding:100px 0 60px;min-height:auto}.lp-hero-text h1{font-size:38px}.lp-hero-brand-name{font-size:28px}.lp-hero-subtitle{font-size:16px}.lp-hero-ctas{flex-direction:column}.lp-hero-ctas .lp-btn{width:100%;justify-content:center}.lp-trust-signals{gap:16px}.lp-section{padding:72px 0}.lp-section-header{margin-bottom:40px}.lp-section-header h2{font-size:28px}.lp-features-grid{grid-template-columns:1fr}.lp-metrics-grid{grid-template-columns:repeat(2,1fr)}.lp-metrics-grid .lp-metric-card{border-right:none;border-bottom:1px solid var(--lp-gray-200)}.lp-metrics-grid .lp-metric-card:last-child{border-bottom:none}.lp-metric-value{font-size:36px}.lp-modules-grid{grid-template-columns:1fr}.lp-steps-grid{flex-direction:column;align-items:center;gap:32px}.lp-step-connector{display:none}.lp-footer-inner{flex-direction:column;align-items:center;text-align:center}}.lp-nav-brand-svg{display:flex;align-items:center;width:36px;height:36px;flex-shrink:0}.lp-nav-brand-svg ::ng-deep svg{width:36px;height:36px;display:block}.lp-nav-brand-wordmark{display:flex;flex-direction:column;line-height:1.1;margin-left:8px}.lp-nav-wordmark-line1{font-size:15px;font-weight:800;color:#1a3a7a;letter-spacing:-.01em;font-family:Outfit,sans-serif}.lp-nav-wordmark-line2{font-size:9px;font-weight:600;letter-spacing:.12em;color:#e8a020;text-transform:uppercase}.lp-hero-bg{position:relative}.lp-hero-watermark{position:absolute;inset:0;display:flex;align-items:flex-start;justify-content:center;padding-top:40px;pointer-events:none;z-index:1}.lp-hero-watermark-inner{width:700px;height:700px;opacity:.04;overflow:hidden;flex-shrink:0}@media (min-width: 768px){.lp-hero-watermark-inner{width:800px;height:800px}}.lp-hero-content{display:flex;flex-direction:column;align-items:center}.lp-hero-main{z-index:1;width:100%}.lp-hero-visual{position:relative;z-index:1;width:100%;max-width:900px;margin:48px auto 0}.lp-workflow-road{position:relative;display:flex;justify-content:space-between;align-items:flex-start;padding:48px 0 24px;gap:16px}.lp-road-line{position:absolute;top:72px;left:6%;right:6%;height:3px;background:linear-gradient(90deg,var(--lp-primary-light),var(--lp-primary),var(--lp-primary-light));border-radius:2px;opacity:.4}.lp-waypoint{flex:1;display:flex;flex-direction:column;align-items:center;text-align:center;position:relative;z-index:1}.lp-waypoint-marker{width:52px;height:52px;border-radius:50%;background:var(--lp-primary);color:#fff;display:flex;align-items:center;justify-content:center;margin-bottom:12px;box-shadow:0 4px 16px color-mix(in srgb,var(--lp-primary) 35%,transparent);transition:transform .25s cubic-bezier(.34,1.56,.64,1)}.lp-waypoint:hover .lp-waypoint-marker{transform:translateY(-4px) scale(1.08)}.lp-waypoint-marker .material-icons{font-size:22px}.lp-waypoint-number{font-size:11px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--lp-primary);margin-bottom:6px;opacity:.7}.lp-waypoint-title{font-size:14px;font-weight:700;color:var(--lp-dark);margin-bottom:6px;font-family:Outfit,sans-serif}.lp-waypoint-desc{font-size:12px;color:var(--lp-gray-600);line-height:1.5;max-width:140px}.lp-section-dark{background:var(--lp-dark);color:#fff}.lp-section-dark.lp-section{padding:96px 0}.lp-header-light h2{color:#fff}.lp-header-light p{color:#ffffffa6}.lp-security-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:32px}.lp-security-card{background:#ffffff0d;border:1px solid rgba(255,255,255,.08);border-radius:20px;padding:32px 28px;transition:background .25s ease}.lp-security-card:hover{background:#ffffff14}.lp-security-icon{width:52px;height:52px;border-radius:14px;background:#ffffff1a;display:flex;align-items:center;justify-content:center;margin-bottom:20px;color:#fff}.lp-security-icon .material-icons{font-size:24px}.lp-security-card h3{font-size:18px;font-weight:700;color:#fff;margin:0 0 12px;font-family:Outfit,sans-serif}.lp-security-card p{font-size:14px;color:#fff9;line-height:1.65;margin:0}.lp-testimonials-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:28px}.lp-testimonial-card{background:#fff;border:1px solid var(--lp-gray-200);border-radius:20px;padding:32px 28px;display:flex;flex-direction:column;gap:16px;box-shadow:0 1px 3px #00000012,0 1px 2px #0000000d;transition:box-shadow .25s ease,transform .25s ease}.lp-testimonial-card:hover{box-shadow:0 4px 8px #0000000f;transform:translateY(-2px)}.lp-stars{display:flex;gap:2px;color:var(--lp-secondary)}.lp-stars .material-icons{font-size:18px}.lp-testimonial-quote{font-size:15px;color:var(--lp-gray-700);line-height:1.65;font-style:italic;margin:0;flex:1}.lp-testimonial-author{display:flex;align-items:center;gap:12px}.lp-author-avatar{width:44px;height:44px;border-radius:50%;background:var(--lp-primary);color:#fff;font-size:14px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-family:Outfit,sans-serif}.lp-author-name{font-size:14px;font-weight:700;color:var(--lp-dark)}.lp-author-role{font-size:12px;color:var(--lp-gray-400)}.lp-pricing-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:28px;align-items:start}.lp-pricing-card{background:#fff;border:1px solid var(--lp-gray-200);border-radius:20px;padding:36px 28px 28px;position:relative;display:flex;flex-direction:column;box-shadow:0 1px 3px #00000012,0 1px 2px #0000000d;transition:box-shadow .25s ease,transform .25s ease}.lp-pricing-card:hover{box-shadow:0 4px 8px #0000000f;transform:translateY(-3px)}.lp-pricing-popular{border-color:var(--lp-primary);box-shadow:0 0 0 2px var(--lp-primary);transform:scale(1.03)}.lp-pricing-popular:hover{transform:scale(1.03) translateY(-3px)}.lp-popular-badge{position:absolute;top:-12px;left:50%;transform:translate(-50%);background:var(--lp-primary);color:#fff;font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;padding:4px 14px;border-radius:100px;white-space:nowrap;font-family:Outfit,sans-serif}.lp-pricing-header{margin-bottom:24px}.lp-pricing-header h3{font-size:20px;font-weight:800;color:var(--lp-dark);margin:0 0 12px;font-family:Outfit,sans-serif}.lp-pricing-price{display:flex;align-items:baseline;gap:4px;margin-bottom:8px}.lp-price-amount{font-size:40px;font-weight:900;color:var(--lp-primary);font-family:Outfit,sans-serif;line-height:1}.lp-price-period{font-size:16px;color:var(--lp-gray-600)}.lp-pricing-desc{font-size:13px;color:var(--lp-gray-600);margin:0}.lp-pricing-features{list-style:none;padding:0;margin:0 0 28px;display:flex;flex-direction:column;gap:10px;flex:1}.lp-pricing-features li{font-size:14px;color:var(--lp-gray-700);padding-left:20px;position:relative}.lp-pricing-features li:before{content:\"\\2713\";position:absolute;left:0;color:var(--lp-primary);font-weight:700}.lp-btn-block{width:100%;justify-content:center;text-align:center}.lp-final-cta{background:linear-gradient(135deg,var(--lp-primary-dark, #152C4A) 0%,var(--lp-primary) 100%);padding:96px 0}.lp-cta-content{text-align:center}.lp-cta-content h2{font-size:40px;font-weight:900;color:#fff;margin:0 0 16px;font-family:Outfit,sans-serif;letter-spacing:-.02em}.lp-cta-content p{font-size:18px;color:#ffffffbf;margin:0 auto 36px;max-width:560px}.lp-cta-buttons{display:flex;gap:16px;justify-content:center;flex-wrap:wrap}.lp-btn-white{background:#fff;color:var(--lp-primary);border:2px solid white;font-weight:700}.lp-btn-white:hover{background:#ffffffe6;transform:translateY(-1px)}.lp-btn-outline-white{background:transparent;color:#fff;border:2px solid rgba(255,255,255,.5);font-weight:700}.lp-btn-outline-white:hover{background:#ffffff1a;border-color:#fff;transform:translateY(-1px)}.lp-footer-grid{display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:48px;padding:64px 0 48px}.lp-footer-brand-col{display:flex;flex-direction:column;gap:16px}.lp-footer-brand-custom{display:flex;align-items:center;gap:10px}.lp-footer-brand-svg{display:flex;align-items:center;width:36px;height:36px;flex-shrink:0}.lp-footer-brand-svg ::ng-deep svg{width:36px;height:36px;display:block}.lp-footer-brand-wordmark{display:flex;flex-direction:column;line-height:1.1}.lp-footer-wordmark-line1{font-size:15px;font-weight:800;color:#fff;font-family:Outfit,sans-serif}.lp-footer-wordmark-line2{font-size:9px;font-weight:600;letter-spacing:.12em;color:#e8a020;text-transform:uppercase}.lp-footer-brand-default{display:flex;align-items:center;gap:10px}.lp-footer-tagline{font-size:14px;color:#ffffff80;line-height:1.6;margin:0;max-width:280px}.lp-footer-col{display:flex;flex-direction:column;gap:12px}.lp-footer-col h4{font-size:13px;font-weight:700;color:#fff;margin:0 0 4px;letter-spacing:.04em;text-transform:uppercase;font-family:Outfit,sans-serif}.lp-footer-col a{font-size:14px;color:#ffffff80;cursor:pointer;text-decoration:none;transition:color .2s}.lp-footer-col a:hover{color:#fff}.lp-footer-bottom{border-top:1px solid rgba(255,255,255,.08);padding:24px 0;font-size:13px;color:#ffffff59}@media (max-width: 768px){.lp-hero-visual{max-width:100%;margin-top:32px}.lp-workflow-road{flex-direction:column;align-items:center;gap:32px}.lp-road-line{display:none}.lp-waypoint-desc{max-width:260px}.lp-security-grid,.lp-testimonials-grid,.lp-pricing-grid{grid-template-columns:1fr}.lp-pricing-popular{transform:none}.lp-pricing-popular:hover{transform:translateY(-3px)}.lp-cta-content h2{font-size:28px}.lp-footer-grid{grid-template-columns:1fr 1fr;gap:32px;padding:48px 0 32px}}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] }); }
16675
+ }
16676
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SpaLandingComponent, decorators: [{
16677
+ type: Component,
16678
+ args: [{ selector: 'spa-landing', standalone: false, template: "<!-- ===== NAVBAR ===== -->\n<nav class=\"lp-navbar\" [class.lp-navbar-scrolled]=\"navScrolled\">\n <div class=\"lp-container lp-nav-inner\">\n <!-- Brand: custom SVG override OR default img+name -->\n <div class=\"lp-nav-brand\" (click)=\"scrollToSection('lp-hero')\">\n <ng-container *ngIf=\"config.navBrand; else defaultNavBrand\">\n <span class=\"lp-nav-brand-svg\" [innerHTML]=\"safeNavBrandSvg\"></span>\n <div *ngIf=\"config.navBrand.wordmarkLine1\" class=\"lp-nav-brand-wordmark\">\n <span class=\"lp-nav-wordmark-line1\">{{ config.navBrand.wordmarkLine1 }}</span>\n <span *ngIf=\"config.navBrand.wordmarkLine2\" class=\"lp-nav-wordmark-line2\">{{ config.navBrand.wordmarkLine2 }}</span>\n </div>\n </ng-container>\n <ng-template #defaultNavBrand>\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-nav-logo\">\n <span class=\"lp-nav-brand-text\">{{ config.appName }}</span>\n </ng-template>\n </div>\n <!-- Desktop nav links -->\n <div class=\"lp-nav-links\">\n <a *ngFor=\"let link of config.navLinks\" (click)=\"scrollToSection(link.target)\">{{ link.label }}</a>\n </div>\n <!-- Mobile menu overlay -->\n <div class=\"lp-mobile-menu\" [class.lp-mobile-menu-open]=\"mobileMenuOpen\">\n <a *ngFor=\"let link of config.navLinks\" (click)=\"scrollToSection(link.target)\">{{ link.label }}</a>\n <div class=\"lp-mobile-auth\">\n <a class=\"lp-nav-login\" (click)=\"navigateTo('login')\">Log in</a>\n <button class=\"lp-btn lp-btn-primary\" (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">Get Started</button>\n </div>\n </div>\n <!-- Desktop auth -->\n <div class=\"lp-nav-auth\">\n <a class=\"lp-nav-login\" (click)=\"navigateTo('login')\">Log in</a>\n <button class=\"lp-btn lp-btn-primary lp-btn-sm\" (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">Get Started</button>\n </div>\n <!-- Hamburger for mobile -->\n <button class=\"lp-hamburger\" (click)=\"toggleMobileMenu()\" [class.lp-hamburger-open]=\"mobileMenuOpen\">\n <span></span><span></span><span></span>\n </button>\n </div>\n</nav>\n\n<!-- ===== HERO ===== -->\n<section id=\"lp-hero\" class=\"lp-hero-bg\" [class.lp-hero-split]=\"config.hero.layout === 'split'\">\n <!-- Optional faded watermark SVG (e.g. company logo) -->\n <!-- Changed: wrapper div gets Angular scoping attribute; inner div holds innerHTML so svg size CSS applies to scoped .lp-hero-watermark-inner -->\n <div *ngIf=\"config.hero.watermarkSvg\" class=\"lp-hero-watermark\" aria-hidden=\"true\">\n <div class=\"lp-hero-watermark-inner\" [innerHTML]=\"safeHeroWatermarkSvg\"></div>\n </div>\n\n <div class=\"lp-container lp-hero-content\">\n <!-- Left / centered column: brand, headline, CTAs -->\n <div class=\"lp-hero-main\">\n <!-- App brand: logo + name + decorative divider -->\n <div class=\"lp-hero-brand\">\n <div class=\"lp-hero-brand-logo\">\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-hero-brand-icon\">\n <span class=\"lp-hero-brand-name\">{{ config.appName }}</span>\n </div>\n <div class=\"lp-hero-divider\" aria-hidden=\"true\">\n <span class=\"lp-hero-divider-line\"></span>\n <span class=\"lp-hero-divider-dot\"></span>\n <span class=\"lp-hero-divider-line\"></span>\n </div>\n </div>\n <!-- Hero text: headline, subtitle, CTAs, trust signals -->\n <div class=\"lp-hero-text\">\n <div *ngIf=\"config.hero.badge\" class=\"lp-hero-badge\">{{ config.hero.badge }}</div>\n <h1>{{ config.hero.headline }} <span class=\"lp-gradient-text\">{{ config.hero.gradientText }}</span></h1>\n <p class=\"lp-hero-subtitle\">{{ config.hero.subtitle }}</p>\n <div class=\"lp-hero-ctas\">\n <button class=\"lp-btn lp-btn-primary lp-btn-lg\" (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">\n {{ config.hero.primaryCTA }}\n </button>\n <button class=\"lp-btn lp-btn-outline lp-btn-lg\" (click)=\"scrollToSection(config.hero.secondaryCTASection)\">\n {{ config.hero.secondaryCTA }}\n </button>\n </div>\n <!-- Trust signals with checkmark SVG icons -->\n <div class=\"lp-trust-signals\">\n <div *ngFor=\"let signal of config.hero.trustSignals\" class=\"lp-trust-item\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path d=\"M20 6L9 17l-5-5\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n <span>{{ signal }}</span>\n </div>\n </div>\n </div>\n </div>\n <!-- App visual: always rendered when projected content exists (stacked below hero text) -->\n <!-- Changed: removed *ngIf so visual always renders below text; layout is always stacked/column -->\n <div class=\"lp-hero-visual\">\n <ng-content select=\"[lpHeroVisual]\"></ng-content>\n </div>\n </div>\n</section>\n\n<!-- ===== METRICS (optional) ===== -->\n<section *ngIf=\"config.metrics\" class=\"lp-section lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-metrics-grid\">\n <div *ngFor=\"let metric of config.metrics\" class=\"lp-metric-card\">\n <div class=\"lp-metric-value\">{{ metric.value }}</div>\n <div class=\"lp-metric-label\">{{ metric.label }}</div>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== FEATURES ===== -->\n<section id=\"lp-features\" class=\"lp-section lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.features.title }}</h2>\n <p>{{ config.features.subtitle }}</p>\n </div>\n <div class=\"lp-features-grid\">\n <div *ngFor=\"let feature of config.features.items\" class=\"lp-feature-card\">\n <!-- Icon tint: hex + '20' = ~12% opacity background -->\n <div class=\"lp-feature-icon\"\n [style.background]=\"iconBg(feature.color)\"\n [style.color]=\"feature.color\">\n <span class=\"material-icons\">{{ feature.icon }}</span>\n </div>\n <h3 class=\"lp-feature-title\">{{ feature.title }}</h3>\n <p class=\"lp-feature-desc\">{{ feature.description }}</p>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== WORKFLOW (optional) ===== -->\n<section *ngIf=\"config.workflow\" id=\"lp-workflow\" class=\"lp-section lp-section-alt lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.workflow!.title }}</h2>\n <p *ngIf=\"config.workflow!.subtitle\">{{ config.workflow!.subtitle }}</p>\n </div>\n\n <!-- Road metaphor layout: horizontal road line with waypoints -->\n <div *ngIf=\"config.workflow!.style === 'road'\" class=\"lp-workflow-road\">\n <div class=\"lp-road-line\"></div>\n <div *ngFor=\"let step of config.workflow!.steps\" class=\"lp-waypoint\">\n <div class=\"lp-waypoint-marker\">\n <span class=\"material-icons\">{{ step.icon }}</span>\n </div>\n <div class=\"lp-waypoint-number\">Step {{ step.number }}</div>\n <div class=\"lp-waypoint-title\">{{ step.title }}</div>\n <div class=\"lp-waypoint-desc\">{{ step.description }}</div>\n </div>\n </div>\n\n <!-- Default steps grid layout -->\n <div *ngIf=\"!config.workflow!.style || config.workflow!.style === 'steps'\" class=\"lp-steps-grid\">\n <div *ngFor=\"let step of config.workflow!.steps; let i = index\" class=\"lp-step-card\">\n <div class=\"lp-step-number\">{{ step.number }}</div>\n <div class=\"lp-step-icon\">\n <span class=\"material-icons\">{{ step.icon }}</span>\n </div>\n <h3 class=\"lp-step-title\">{{ step.title }}</h3>\n <p class=\"lp-step-desc\">{{ step.description }}</p>\n <!-- Connector line between steps, not after last -->\n <div *ngIf=\"i < config.workflow!.steps.length - 1\" class=\"lp-step-connector\"></div>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== CUSTOM SECTION SLOT ===== -->\n<!-- Consumer projects app-specific sections here (fleet board, benefits rows, etc.)\n using [lpCustomSection] attribute on a wrapper element -->\n<ng-content select=\"[lpCustomSection]\"></ng-content>\n\n<!-- ===== MODULES (optional) ===== -->\n<section *ngIf=\"config.modules\" id=\"lp-modules\" class=\"lp-section lp-section-alt lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.modules!.title }}</h2>\n <p>{{ config.modules!.subtitle }}</p>\n </div>\n <div class=\"lp-modules-grid\">\n <div *ngFor=\"let group of config.modules!.groups\" class=\"lp-module-group\">\n <div class=\"lp-module-group-label\">{{ group.category }}</div>\n <div class=\"lp-module-tiles\">\n <div *ngFor=\"let tile of group.tiles\" class=\"lp-module-tile\">\n <span class=\"material-icons\">{{ tile.icon }}</span>\n <span>{{ tile.name }}</span>\n </div>\n </div>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== SECURITY (optional \u2014 dark background) ===== -->\n<section *ngIf=\"config.security\" class=\"lp-section lp-section-dark lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header lp-header-light\">\n <h2>{{ config.security!.title }}</h2>\n <p>{{ config.security!.subtitle }}</p>\n </div>\n <div class=\"lp-security-grid\">\n <div *ngFor=\"let item of config.security!.items\" class=\"lp-security-card\">\n <div class=\"lp-security-icon\">\n <span class=\"material-icons\">{{ item.icon }}</span>\n </div>\n <h3>{{ item.title }}</h3>\n <p>{{ item.description }}</p>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== TESTIMONIALS (optional) ===== -->\n<section *ngIf=\"config.testimonials\" class=\"lp-section lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.testimonials!.title }}</h2>\n <p *ngIf=\"config.testimonials!.subtitle\">{{ config.testimonials!.subtitle }}</p>\n </div>\n <div class=\"lp-testimonials-grid\">\n <div *ngFor=\"let item of config.testimonials!.items\" class=\"lp-testimonial-card\">\n <div class=\"lp-stars\">\n <span class=\"material-icons\">star</span>\n <span class=\"material-icons\">star</span>\n <span class=\"material-icons\">star</span>\n <span class=\"material-icons\">star</span>\n <span class=\"material-icons\">star</span>\n </div>\n <p class=\"lp-testimonial-quote\">\"{{ item.quote }}\"</p>\n <div class=\"lp-testimonial-author\">\n <div class=\"lp-author-avatar\">{{ item.authorInitials }}</div>\n <div>\n <div class=\"lp-author-name\">{{ item.authorName }}</div>\n <div class=\"lp-author-role\">{{ item.authorRole }}</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== PRICING (optional) ===== -->\n<section *ngIf=\"config.pricing\" id=\"lp-pricing\" class=\"lp-section lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.pricing!.title }}</h2>\n <p *ngIf=\"config.pricing!.subtitle\">{{ config.pricing!.subtitle }}</p>\n </div>\n <div class=\"lp-pricing-grid\">\n <div *ngFor=\"let plan of config.pricing!.plans\"\n class=\"lp-pricing-card\"\n [class.lp-pricing-popular]=\"plan.popular\">\n <div *ngIf=\"plan.popular\" class=\"lp-popular-badge\">Most Popular</div>\n <div class=\"lp-pricing-header\">\n <h3>{{ plan.name }}</h3>\n <div class=\"lp-pricing-price\">\n <span class=\"lp-price-amount\">{{ plan.price }}</span>\n <span *ngIf=\"plan.period\" class=\"lp-price-period\">{{ plan.period }}</span>\n </div>\n <p class=\"lp-pricing-desc\">{{ plan.description }}</p>\n </div>\n <ul class=\"lp-pricing-features\">\n <li *ngFor=\"let feature of plan.features\">{{ feature }}</li>\n </ul>\n <!-- Popular plans get primary button, others get outline -->\n <button *ngIf=\"plan.popular\"\n class=\"lp-btn lp-btn-primary lp-btn-block\"\n (click)=\"plan.ctaRoute ? navigateTo(plan.ctaRoute) : scrollToSection(plan.ctaSection || '')\">\n {{ plan.ctaLabel }}\n </button>\n <button *ngIf=\"!plan.popular\"\n class=\"lp-btn lp-btn-outline lp-btn-block\"\n (click)=\"plan.ctaRoute ? navigateTo(plan.ctaRoute) : scrollToSection(plan.ctaSection || '')\">\n {{ plan.ctaLabel }}\n </button>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== FINAL CTA BANNER (optional) ===== -->\n<section *ngIf=\"config.finalCta\" class=\"lp-final-cta lp-animate\">\n <div class=\"lp-container lp-cta-content\">\n <h2>{{ config.finalCta!.headline }}</h2>\n <p>{{ config.finalCta!.subtitle }}</p>\n <div class=\"lp-cta-buttons\">\n <button class=\"lp-btn lp-btn-white lp-btn-lg\"\n (click)=\"navigateTo(config.finalCta!.primaryCTARoute || 'signup')\">\n {{ config.finalCta!.primaryCTA }}\n </button>\n <button *ngIf=\"config.finalCta!.secondaryCTA\"\n class=\"lp-btn lp-btn-outline-white lp-btn-lg\"\n (click)=\"scrollToSection(config.finalCta!.secondaryCTASection || '')\">\n {{ config.finalCta!.secondaryCTA }}\n </button>\n </div>\n </div>\n</section>\n\n<!-- ===== FOOTER ===== -->\n<footer class=\"lp-footer\" id=\"lp-footer\">\n <div class=\"lp-container\">\n\n <!-- Rich multi-column footer when footerColumns provided -->\n <div *ngIf=\"config.footerColumns && config.footerColumns.length; else simpleFooter\" class=\"lp-footer-grid\">\n <!-- Brand column -->\n <div class=\"lp-footer-brand-col\">\n <!-- Custom SVG footer brand OR default img+name -->\n <ng-container *ngIf=\"config.footerBrand; else defaultFooterBrand\">\n <div class=\"lp-footer-brand-custom\">\n <span class=\"lp-footer-brand-svg\" [innerHTML]=\"safeFooterBrandSvg\"></span>\n <div *ngIf=\"config.footerBrand.wordmarkLine1\" class=\"lp-footer-brand-wordmark\">\n <span class=\"lp-footer-wordmark-line1\">{{ config.footerBrand.wordmarkLine1 }}</span>\n <span *ngIf=\"config.footerBrand.wordmarkLine2\" class=\"lp-footer-wordmark-line2\">{{ config.footerBrand.wordmarkLine2 }}</span>\n </div>\n </div>\n </ng-container>\n <ng-template #defaultFooterBrand>\n <div class=\"lp-footer-brand-default\">\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-footer-logo\">\n <span class=\"lp-footer-name\">{{ config.appName }}</span>\n </div>\n </ng-template>\n <p *ngIf=\"config.footerTagline\" class=\"lp-footer-tagline\">{{ config.footerTagline }}</p>\n </div>\n <!-- Link columns -->\n <div *ngFor=\"let col of config.footerColumns\" class=\"lp-footer-col\">\n <h4>{{ col.title }}</h4>\n <a *ngFor=\"let link of col.links\"\n (click)=\"link.section ? scrollToSection(link.section) : (link.route ? navigateTo(link.route) : null)\">\n {{ link.label }}\n </a>\n </div>\n </div>\n\n <!-- Simple single-row footer (default) -->\n <ng-template #simpleFooter>\n <div class=\"lp-footer-inner\">\n <div class=\"lp-footer-brand\">\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-footer-logo\">\n <span class=\"lp-footer-name\">{{ config.appName }}</span>\n </div>\n <div class=\"lp-footer-links\">\n <a (click)=\"navigateTo('login')\">Log in</a>\n <a (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">Sign up</a>\n </div>\n <div class=\"lp-footer-copy\">\n &copy; {{ currentYear }} {{ config.footerCompany || 'alsquare technologies' }}. All rights reserved.\n </div>\n </div>\n </ng-template>\n\n <!-- Footer bottom bar (shown for both layouts) -->\n <div *ngIf=\"config.footerColumns && config.footerColumns.length\" class=\"lp-footer-bottom\">\n <span>&copy; {{ currentYear }} {{ config.footerCompany || 'alsquare technologies' }}. All rights reserved.</span>\n </div>\n </div>\n</footer>\n", styles: ["@import\"https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800;900&display=swap\";:host{--lp-primary: #1E3A5F;--lp-primary-dark: #152C4A;--lp-primary-light: #EBF0F7;--lp-secondary: #F59E0B;--lp-secondary-light:#FEF3C7;--lp-dark: #0D1117;--lp-gray-800: #1F2937;--lp-gray-700: #374151;--lp-gray-600: #4B5563;--lp-gray-400: #9CA3AF;--lp-gray-200: #E5E7EB;--lp-gray-100: #F3F4F6;--lp-gray-50: #F9FAFB;--lp-white: #FFFFFF;--lp-radius: 12px;--lp-radius-sm: 8px;--lp-radius-lg: 20px;--lp-radius-xl: 28px;--lp-shadow-xs: 0 1px 2px rgba(0,0,0,.05);--lp-shadow: 0 1px 3px rgba(0,0,0,.07), 0 1px 2px rgba(0,0,0,.05);--lp-shadow-md: 0 4px 8px rgba(0,0,0,.06), 0 2px 4px rgba(0,0,0,.04);--lp-shadow-lg: 0 12px 28px rgba(0,0,0,.1), 0 4px 8px rgba(0,0,0,.06);--lp-shadow-xl: 0 20px 40px rgba(0,0,0,.12), 0 8px 16px rgba(0,0,0,.07);--lp-ease: cubic-bezier(.4, 0, .2, 1);--lp-spring: cubic-bezier(.34, 1.56, .64, 1);display:block;font-family:Outfit,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;color:var(--lp-gray-700);line-height:1.6;overflow-x:hidden;-webkit-font-smoothing:antialiased}.lp-container{max-width:1200px;margin:0 auto;padding:0 32px}.lp-navbar{position:fixed;top:0;left:0;right:0;z-index:1000;padding:18px 0;transition:background .3s var(--lp-ease),padding .3s var(--lp-ease),box-shadow .3s var(--lp-ease)}.lp-navbar-scrolled{background:#ffffffeb;backdrop-filter:blur(16px) saturate(180%);-webkit-backdrop-filter:blur(16px) saturate(180%);box-shadow:0 1px #0000000f,0 2px 8px #0000000a;padding:12px 0}.lp-nav-inner{display:flex;align-items:center;justify-content:space-between}.lp-nav-brand{display:flex;align-items:center;gap:10px;cursor:pointer;text-decoration:none}.lp-nav-logo{width:30px;height:30px;border-radius:7px;object-fit:contain}.lp-nav-brand-text{font-size:18px;font-weight:700;color:var(--lp-primary);letter-spacing:-.01em}.lp-nav-links{display:flex;align-items:center;gap:36px}.lp-nav-links a{font-size:14px;font-weight:500;color:var(--lp-gray-600);cursor:pointer;transition:color .2s;text-decoration:none;letter-spacing:.01em}.lp-nav-links a:hover{color:var(--lp-primary)}.lp-nav-auth{display:flex;align-items:center;gap:12px}.lp-nav-login{font-size:14px;font-weight:500;color:var(--lp-gray-600);cursor:pointer;transition:color .2s;text-decoration:none}.lp-nav-login:hover{color:var(--lp-primary)}.lp-hamburger{display:none;flex-direction:column;gap:5px;background:none;border:none;cursor:pointer;padding:4px;z-index:1001}.lp-hamburger span{display:block;width:22px;height:2px;background:var(--lp-gray-700);border-radius:2px;transition:transform .3s var(--lp-ease),opacity .3s}.lp-hamburger-open span:nth-child(1){transform:translateY(7px) rotate(45deg)}.lp-hamburger-open span:nth-child(2){opacity:0}.lp-hamburger-open span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}.lp-mobile-menu{display:none}.lp-mobile-auth{display:flex;flex-direction:column;gap:12px;margin-top:24px;padding-top:24px;border-top:1px solid var(--lp-gray-200)}.lp-btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;font-family:Outfit,sans-serif;font-size:14px;font-weight:600;padding:10px 22px;border-radius:var(--lp-radius-sm);border:none;cursor:pointer;transition:all .25s var(--lp-ease);text-decoration:none;white-space:nowrap;letter-spacing:.01em}.lp-btn-primary{background:var(--lp-primary);color:var(--lp-white);box-shadow:0 1px 3px #00000026,inset 0 1px #ffffff1f}.lp-btn-primary:hover{background:var(--lp-primary-dark);transform:translateY(-1px);box-shadow:0 6px 16px #0003,inset 0 1px #ffffff1f}.lp-btn-primary:active{transform:translateY(0)}.lp-btn-outline{background:transparent;color:var(--lp-primary);border:1.5px solid var(--lp-primary)}.lp-btn-outline:hover{background:var(--lp-primary);color:var(--lp-white);transform:translateY(-1px);box-shadow:0 4px 12px #00000026}.lp-btn-sm{padding:7px 16px;font-size:13px}.lp-btn-lg{padding:14px 32px;font-size:15px;border-radius:var(--lp-radius)}.lp-btn-block{width:100%}.lp-hero-bg{position:relative;overflow:hidden;padding:140px 0 100px;background:radial-gradient(ellipse 80% 60% at 60% -10%,color-mix(in srgb,var(--lp-primary) 8%,transparent) 0%,transparent 70%),radial-gradient(ellipse 60% 50% at -10% 80%,color-mix(in srgb,var(--lp-secondary) 6%,transparent) 0%,transparent 70%),linear-gradient(160deg,var(--lp-primary-light) 0%,#FAFBFF 45%,var(--lp-secondary-light) 100%);min-height:100vh;display:flex;align-items:center}.lp-hero-bg:before{content:\"\";position:absolute;inset:0;background-image:radial-gradient(circle,rgba(0,0,0,.04) 1px,transparent 1px);background-size:32px 32px;pointer-events:none;-webkit-mask-image:radial-gradient(ellipse 80% 80% at 50% 50%,black 40%,transparent 100%);mask-image:radial-gradient(ellipse 80% 80% at 50% 50%,black 40%,transparent 100%)}.lp-hero-bg:after{content:\"\";position:absolute;width:700px;height:700px;border-radius:50%;background:color-mix(in srgb,var(--lp-primary) 5%,transparent);top:-200px;right:-150px;filter:blur(100px);pointer-events:none}.lp-hero-content{position:relative;z-index:1;text-align:center;width:100%}.lp-hero-badge{display:inline-flex;align-items:center;gap:6px;background:color-mix(in srgb,var(--lp-primary) 10%,transparent);color:var(--lp-primary);border:1px solid color-mix(in srgb,var(--lp-primary) 20%,transparent);border-radius:100px;padding:6px 16px;font-size:13px;font-weight:600;margin-bottom:20px;letter-spacing:.02em}.lp-hero-brand{margin-bottom:20px;text-align:center}.lp-hero-brand-logo{display:inline-flex;align-items:center;gap:14px;margin-bottom:12px}.lp-hero-brand-icon{height:52px;width:52px;border-radius:14px;object-fit:contain;box-shadow:0 4px 12px #0000001f}.lp-hero-brand-name{font-size:38px;font-weight:800;color:var(--lp-primary);letter-spacing:-.03em;font-family:Outfit,sans-serif}.lp-hero-divider{display:flex;align-items:center;justify-content:center;gap:10px}.lp-hero-divider-line{display:block;height:1px;width:40px;background:linear-gradient(90deg,transparent,var(--lp-primary),transparent);opacity:.3}.lp-hero-divider-dot{display:block;height:5px;width:5px;border-radius:50%;background:var(--lp-secondary);opacity:.8}.lp-hero-text{max-width:760px;margin:0 auto 64px}.lp-hero-text h1{font-family:Outfit,sans-serif;font-size:68px;font-weight:900;line-height:1.05;color:var(--lp-dark);margin:0 0 24px;letter-spacing:-.03em}.lp-gradient-text{background:linear-gradient(135deg,var(--lp-primary) 0%,var(--lp-secondary) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}.lp-hero-subtitle{font-size:19px;color:var(--lp-gray-600);line-height:1.65;font-weight:400;max-width:560px;margin:0 auto 36px}.lp-hero-ctas{display:flex;align-items:center;justify-content:center;gap:14px;margin-bottom:32px;flex-wrap:wrap}.lp-trust-signals{display:flex;align-items:center;justify-content:center;gap:28px;flex-wrap:wrap}.lp-trust-item{display:flex;align-items:center;gap:7px;font-size:13px;font-weight:500;color:var(--lp-gray-500, #6B7280)}.lp-trust-item svg{color:var(--lp-primary);flex-shrink:0}.lp-section{padding:100px 0;background:var(--lp-white)}.lp-section-alt{background:var(--lp-gray-50)}.lp-section-header{text-align:center;margin-bottom:64px}.lp-section-header h2{font-family:Outfit,sans-serif;font-size:42px;font-weight:800;color:var(--lp-dark);margin:0 0 14px;letter-spacing:-.025em;line-height:1.15}.lp-section-header p{font-size:17px;color:var(--lp-gray-600);max-width:540px;margin:0 auto;line-height:1.65}.lp-features-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:20px}.lp-feature-card{background:var(--lp-white);border-radius:var(--lp-radius-lg);padding:36px 28px;transition:transform .25s var(--lp-ease),box-shadow .25s var(--lp-ease);border:1px solid var(--lp-gray-200);box-shadow:var(--lp-shadow-xs);position:relative;overflow:hidden}.lp-feature-card:before{content:\"\";position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,var(--lp-primary),var(--lp-secondary));transform:scaleX(0);transform-origin:left;transition:transform .3s var(--lp-ease);border-radius:3px 3px 0 0}.lp-feature-card:hover{transform:translateY(-5px);box-shadow:var(--lp-shadow-xl);border-color:transparent}.lp-feature-card:hover:before{transform:scaleX(1)}.lp-feature-icon{width:52px;height:52px;border-radius:var(--lp-radius-sm);display:flex;align-items:center;justify-content:center;margin-bottom:20px}.lp-feature-icon .material-icons{font-size:26px}.lp-feature-title{font-family:Outfit,sans-serif;font-size:17px;font-weight:700;color:var(--lp-dark);margin:0 0 10px;letter-spacing:-.01em}.lp-feature-desc{font-size:14px;color:var(--lp-gray-600);line-height:1.65;margin:0}.lp-steps-grid{display:flex;align-items:flex-start;justify-content:space-between;position:relative;gap:16px}.lp-step-card{flex:1;display:flex;flex-direction:column;align-items:center;text-align:center;position:relative;padding:0 8px}.lp-step-number{font-size:11px;font-weight:700;color:var(--lp-secondary);text-transform:uppercase;letter-spacing:2px;margin-bottom:10px}.lp-step-icon{width:56px;height:56px;border-radius:50%;background:var(--lp-primary);display:flex;align-items:center;justify-content:center;margin-bottom:18px;box-shadow:0 4px 14px color-mix(in srgb,var(--lp-primary) 30%,transparent);border:3px solid var(--lp-white);position:relative;z-index:1}.lp-step-icon .material-icons{font-size:24px;color:var(--lp-white)}.lp-step-title{font-family:Outfit,sans-serif;font-size:15px;font-weight:700;color:var(--lp-dark);margin:0 0 8px;letter-spacing:-.01em}.lp-step-desc{font-size:13px;color:var(--lp-gray-600);line-height:1.55;margin:0}.lp-step-connector{position:absolute;top:40px;left:60%;width:80%;height:2px;background:repeating-linear-gradient(90deg,color-mix(in srgb,var(--lp-secondary) 50%,transparent) 0px,color-mix(in srgb,var(--lp-secondary) 50%,transparent) 10px,transparent 10px,transparent 18px);pointer-events:none;z-index:0}.lp-metrics-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:0;border:1px solid var(--lp-gray-200);border-radius:var(--lp-radius-lg);overflow:hidden;background:var(--lp-white);box-shadow:var(--lp-shadow)}.lp-metric-card{text-align:center;padding:40px 24px;border-right:1px solid var(--lp-gray-200);transition:background .2s}.lp-metric-card:last-child{border-right:none}.lp-metric-card:hover{background:var(--lp-gray-50)}.lp-metric-value{font-family:Outfit,sans-serif;font-size:48px;font-weight:900;color:var(--lp-primary);line-height:1;letter-spacing:-.03em;margin-bottom:8px}.lp-metric-label{font-size:14px;font-weight:500;color:var(--lp-gray-600)}.lp-modules-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:20px}.lp-module-group{background:var(--lp-white);border-radius:var(--lp-radius-lg);padding:28px 28px 24px;border:1px solid var(--lp-gray-200);box-shadow:var(--lp-shadow-xs);transition:box-shadow .2s}.lp-module-group:hover{box-shadow:var(--lp-shadow-md)}.lp-module-group-label{font-family:Outfit,sans-serif;font-size:11px;font-weight:700;color:var(--lp-primary);margin-bottom:16px;text-transform:uppercase;letter-spacing:1px}.lp-module-tiles{display:flex;flex-wrap:wrap;gap:8px}.lp-module-tile{display:inline-flex;align-items:center;gap:6px;background:var(--lp-gray-50);border:1px solid var(--lp-gray-200);padding:7px 13px;border-radius:100px;font-size:12px;font-weight:600;color:var(--lp-gray-700);transition:all .2s var(--lp-ease);cursor:default}.lp-module-tile:hover{background:color-mix(in srgb,var(--lp-primary) 6%,transparent);border-color:color-mix(in srgb,var(--lp-primary) 25%,transparent);color:var(--lp-primary);transform:translateY(-1px)}.lp-module-tile .material-icons{font-size:15px;color:var(--lp-primary)}.lp-footer{background:var(--lp-dark);padding:44px 0 36px;border-top:1px solid rgba(255,255,255,.04)}.lp-footer-inner{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:20px}.lp-footer-brand{display:flex;align-items:center;gap:10px}.lp-footer-logo{width:30px;height:30px;border-radius:7px;object-fit:contain;opacity:.9}.lp-footer-name{font-family:Outfit,sans-serif;font-size:17px;font-weight:700;color:var(--lp-white);letter-spacing:-.01em}.lp-footer-links{display:flex;gap:28px}.lp-footer-links a{font-size:14px;font-weight:500;color:#ffffff80;cursor:pointer;text-decoration:none;transition:color .2s}.lp-footer-links a:hover{color:var(--lp-white)}.lp-footer-copy{font-size:13px;color:#ffffff59;width:100%;text-align:center;padding-top:20px;margin-top:4px;border-top:1px solid rgba(255,255,255,.06)}.lp-animate{opacity:0;transform:translateY(28px);transition:opacity .65s var(--lp-ease),transform .65s var(--lp-ease)}.lp-visible{opacity:1;transform:translateY(0)}.lp-visible .lp-feature-card:nth-child(1){transition-delay:0ms}.lp-visible .lp-feature-card:nth-child(2){transition-delay:60ms}.lp-visible .lp-feature-card:nth-child(3){transition-delay:.12s}.lp-visible .lp-feature-card:nth-child(4){transition-delay:.18s}.lp-visible .lp-feature-card:nth-child(5){transition-delay:.24s}.lp-visible .lp-feature-card:nth-child(6){transition-delay:.3s}.lp-visible .lp-step-card:nth-child(1){transition-delay:0ms}.lp-visible .lp-step-card:nth-child(2){transition-delay:80ms}.lp-visible .lp-step-card:nth-child(3){transition-delay:.16s}.lp-visible .lp-step-card:nth-child(4){transition-delay:.24s}.lp-visible .lp-metric-card:nth-child(1){transition-delay:0ms}.lp-visible .lp-metric-card:nth-child(2){transition-delay:60ms}.lp-visible .lp-metric-card:nth-child(3){transition-delay:.12s}.lp-visible .lp-metric-card:nth-child(4){transition-delay:.18s}@media (max-width: 1024px){.lp-hero-text h1{font-size:52px}.lp-section-header h2{font-size:36px}.lp-features-grid,.lp-metrics-grid{grid-template-columns:repeat(2,1fr)}.lp-metrics-grid .lp-metric-card:nth-child(2){border-right:none}.lp-metrics-grid .lp-metric-card:nth-child(3){border-top:1px solid var(--lp-gray-200)}.lp-metrics-grid .lp-metric-card:nth-child(4){border-top:1px solid var(--lp-gray-200)}.lp-steps-grid{flex-wrap:wrap;justify-content:center;gap:40px}.lp-step-connector{display:none}}@media (max-width: 767px){.lp-container{padding:0 20px}.lp-nav-links{display:none}.lp-hamburger{display:flex}.lp-mobile-auth{display:none}.lp-mobile-menu{display:none;position:fixed;inset:0;background:#fffffff7;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);flex-direction:column;align-items:center;justify-content:center;gap:28px;z-index:999}.lp-mobile-menu-open{display:flex}.lp-mobile-menu a{font-size:22px;font-weight:700;color:var(--lp-dark);cursor:pointer;text-decoration:none;letter-spacing:-.01em}.lp-hero-bg{padding:100px 0 60px;min-height:auto}.lp-hero-text h1{font-size:38px}.lp-hero-brand-name{font-size:28px}.lp-hero-subtitle{font-size:16px}.lp-hero-ctas{flex-direction:column}.lp-hero-ctas .lp-btn{width:100%;justify-content:center}.lp-trust-signals{gap:16px}.lp-section{padding:72px 0}.lp-section-header{margin-bottom:40px}.lp-section-header h2{font-size:28px}.lp-features-grid{grid-template-columns:1fr}.lp-metrics-grid{grid-template-columns:repeat(2,1fr)}.lp-metrics-grid .lp-metric-card{border-right:none;border-bottom:1px solid var(--lp-gray-200)}.lp-metrics-grid .lp-metric-card:last-child{border-bottom:none}.lp-metric-value{font-size:36px}.lp-modules-grid{grid-template-columns:1fr}.lp-steps-grid{flex-direction:column;align-items:center;gap:32px}.lp-step-connector{display:none}.lp-footer-inner{flex-direction:column;align-items:center;text-align:center}}.lp-nav-brand-svg{display:flex;align-items:center;width:36px;height:36px;flex-shrink:0}.lp-nav-brand-svg ::ng-deep svg{width:36px;height:36px;display:block}.lp-nav-brand-wordmark{display:flex;flex-direction:column;line-height:1.1;margin-left:8px}.lp-nav-wordmark-line1{font-size:15px;font-weight:800;color:#1a3a7a;letter-spacing:-.01em;font-family:Outfit,sans-serif}.lp-nav-wordmark-line2{font-size:9px;font-weight:600;letter-spacing:.12em;color:#e8a020;text-transform:uppercase}.lp-hero-bg{position:relative}.lp-hero-watermark{position:absolute;inset:0;display:flex;align-items:flex-start;justify-content:center;padding-top:40px;pointer-events:none;z-index:1}.lp-hero-watermark-inner{width:700px;height:700px;opacity:.04;overflow:hidden;flex-shrink:0}@media (min-width: 768px){.lp-hero-watermark-inner{width:800px;height:800px}}.lp-hero-content{display:flex;flex-direction:column;align-items:center}.lp-hero-main{z-index:1;width:100%}.lp-hero-visual{position:relative;z-index:1;width:100%;max-width:900px;margin:48px auto 0}.lp-workflow-road{position:relative;display:flex;justify-content:space-between;align-items:flex-start;padding:48px 0 24px;gap:16px}.lp-road-line{position:absolute;top:72px;left:6%;right:6%;height:3px;background:linear-gradient(90deg,var(--lp-primary-light),var(--lp-primary),var(--lp-primary-light));border-radius:2px;opacity:.4}.lp-waypoint{flex:1;display:flex;flex-direction:column;align-items:center;text-align:center;position:relative;z-index:1}.lp-waypoint-marker{width:52px;height:52px;border-radius:50%;background:var(--lp-primary);color:#fff;display:flex;align-items:center;justify-content:center;margin-bottom:12px;box-shadow:0 4px 16px color-mix(in srgb,var(--lp-primary) 35%,transparent);transition:transform .25s cubic-bezier(.34,1.56,.64,1)}.lp-waypoint:hover .lp-waypoint-marker{transform:translateY(-4px) scale(1.08)}.lp-waypoint-marker .material-icons{font-size:22px}.lp-waypoint-number{font-size:11px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--lp-primary);margin-bottom:6px;opacity:.7}.lp-waypoint-title{font-size:14px;font-weight:700;color:var(--lp-dark);margin-bottom:6px;font-family:Outfit,sans-serif}.lp-waypoint-desc{font-size:12px;color:var(--lp-gray-600);line-height:1.5;max-width:140px}.lp-section-dark{background:var(--lp-dark);color:#fff}.lp-section-dark.lp-section{padding:96px 0}.lp-header-light h2{color:#fff}.lp-header-light p{color:#ffffffa6}.lp-security-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:32px}.lp-security-card{background:#ffffff0d;border:1px solid rgba(255,255,255,.08);border-radius:20px;padding:32px 28px;transition:background .25s ease}.lp-security-card:hover{background:#ffffff14}.lp-security-icon{width:52px;height:52px;border-radius:14px;background:#ffffff1a;display:flex;align-items:center;justify-content:center;margin-bottom:20px;color:#fff}.lp-security-icon .material-icons{font-size:24px}.lp-security-card h3{font-size:18px;font-weight:700;color:#fff;margin:0 0 12px;font-family:Outfit,sans-serif}.lp-security-card p{font-size:14px;color:#fff9;line-height:1.65;margin:0}.lp-testimonials-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:28px}.lp-testimonial-card{background:#fff;border:1px solid var(--lp-gray-200);border-radius:20px;padding:32px 28px;display:flex;flex-direction:column;gap:16px;box-shadow:0 1px 3px #00000012,0 1px 2px #0000000d;transition:box-shadow .25s ease,transform .25s ease}.lp-testimonial-card:hover{box-shadow:0 4px 8px #0000000f;transform:translateY(-2px)}.lp-stars{display:flex;gap:2px;color:var(--lp-secondary)}.lp-stars .material-icons{font-size:18px}.lp-testimonial-quote{font-size:15px;color:var(--lp-gray-700);line-height:1.65;font-style:italic;margin:0;flex:1}.lp-testimonial-author{display:flex;align-items:center;gap:12px}.lp-author-avatar{width:44px;height:44px;border-radius:50%;background:var(--lp-primary);color:#fff;font-size:14px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-family:Outfit,sans-serif}.lp-author-name{font-size:14px;font-weight:700;color:var(--lp-dark)}.lp-author-role{font-size:12px;color:var(--lp-gray-400)}.lp-pricing-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:28px;align-items:start}.lp-pricing-card{background:#fff;border:1px solid var(--lp-gray-200);border-radius:20px;padding:36px 28px 28px;position:relative;display:flex;flex-direction:column;box-shadow:0 1px 3px #00000012,0 1px 2px #0000000d;transition:box-shadow .25s ease,transform .25s ease}.lp-pricing-card:hover{box-shadow:0 4px 8px #0000000f;transform:translateY(-3px)}.lp-pricing-popular{border-color:var(--lp-primary);box-shadow:0 0 0 2px var(--lp-primary);transform:scale(1.03)}.lp-pricing-popular:hover{transform:scale(1.03) translateY(-3px)}.lp-popular-badge{position:absolute;top:-12px;left:50%;transform:translate(-50%);background:var(--lp-primary);color:#fff;font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;padding:4px 14px;border-radius:100px;white-space:nowrap;font-family:Outfit,sans-serif}.lp-pricing-header{margin-bottom:24px}.lp-pricing-header h3{font-size:20px;font-weight:800;color:var(--lp-dark);margin:0 0 12px;font-family:Outfit,sans-serif}.lp-pricing-price{display:flex;align-items:baseline;gap:4px;margin-bottom:8px}.lp-price-amount{font-size:40px;font-weight:900;color:var(--lp-primary);font-family:Outfit,sans-serif;line-height:1}.lp-price-period{font-size:16px;color:var(--lp-gray-600)}.lp-pricing-desc{font-size:13px;color:var(--lp-gray-600);margin:0}.lp-pricing-features{list-style:none;padding:0;margin:0 0 28px;display:flex;flex-direction:column;gap:10px;flex:1}.lp-pricing-features li{font-size:14px;color:var(--lp-gray-700);padding-left:20px;position:relative}.lp-pricing-features li:before{content:\"\\2713\";position:absolute;left:0;color:var(--lp-primary);font-weight:700}.lp-btn-block{width:100%;justify-content:center;text-align:center}.lp-final-cta{background:linear-gradient(135deg,var(--lp-primary-dark, #152C4A) 0%,var(--lp-primary) 100%);padding:96px 0}.lp-cta-content{text-align:center}.lp-cta-content h2{font-size:40px;font-weight:900;color:#fff;margin:0 0 16px;font-family:Outfit,sans-serif;letter-spacing:-.02em}.lp-cta-content p{font-size:18px;color:#ffffffbf;margin:0 auto 36px;max-width:560px}.lp-cta-buttons{display:flex;gap:16px;justify-content:center;flex-wrap:wrap}.lp-btn-white{background:#fff;color:var(--lp-primary);border:2px solid white;font-weight:700}.lp-btn-white:hover{background:#ffffffe6;transform:translateY(-1px)}.lp-btn-outline-white{background:transparent;color:#fff;border:2px solid rgba(255,255,255,.5);font-weight:700}.lp-btn-outline-white:hover{background:#ffffff1a;border-color:#fff;transform:translateY(-1px)}.lp-footer-grid{display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:48px;padding:64px 0 48px}.lp-footer-brand-col{display:flex;flex-direction:column;gap:16px}.lp-footer-brand-custom{display:flex;align-items:center;gap:10px}.lp-footer-brand-svg{display:flex;align-items:center;width:36px;height:36px;flex-shrink:0}.lp-footer-brand-svg ::ng-deep svg{width:36px;height:36px;display:block}.lp-footer-brand-wordmark{display:flex;flex-direction:column;line-height:1.1}.lp-footer-wordmark-line1{font-size:15px;font-weight:800;color:#fff;font-family:Outfit,sans-serif}.lp-footer-wordmark-line2{font-size:9px;font-weight:600;letter-spacing:.12em;color:#e8a020;text-transform:uppercase}.lp-footer-brand-default{display:flex;align-items:center;gap:10px}.lp-footer-tagline{font-size:14px;color:#ffffff80;line-height:1.6;margin:0;max-width:280px}.lp-footer-col{display:flex;flex-direction:column;gap:12px}.lp-footer-col h4{font-size:13px;font-weight:700;color:#fff;margin:0 0 4px;letter-spacing:.04em;text-transform:uppercase;font-family:Outfit,sans-serif}.lp-footer-col a{font-size:14px;color:#ffffff80;cursor:pointer;text-decoration:none;transition:color .2s}.lp-footer-col a:hover{color:#fff}.lp-footer-bottom{border-top:1px solid rgba(255,255,255,.08);padding:24px 0;font-size:13px;color:#ffffff59}@media (max-width: 768px){.lp-hero-visual{max-width:100%;margin-top:32px}.lp-workflow-road{flex-direction:column;align-items:center;gap:32px}.lp-road-line{display:none}.lp-waypoint-desc{max-width:260px}.lp-security-grid,.lp-testimonials-grid,.lp-pricing-grid{grid-template-columns:1fr}.lp-pricing-popular{transform:none}.lp-pricing-popular:hover{transform:translateY(-3px)}.lp-cta-content h2{font-size:28px}.lp-footer-grid{grid-template-columns:1fr 1fr;gap:32px;padding:48px 0 32px}}\n"] }]
16679
+ }], ctorParameters: () => [{ type: i1$2.Router }, { type: AuthService }, { type: i0.ElementRef }, { type: i1$5.DomSanitizer }], propDecorators: { config: [{
16680
+ type: Input
16681
+ }], onWindowScroll: [{
16682
+ type: HostListener,
16683
+ args: ['window:scroll']
16684
+ }] } });
16685
+
16224
16686
  class TinSpaModule {
16225
16687
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TinSpaModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
16226
16688
  static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.14", ngImport: i0, type: TinSpaModule, declarations: [TinSpaComponent, TextComponent, TextMaskComponent, TextAreaComponent, TextSingleComponent, CheckComponent, DateComponent, DatetimeComponent, LabelComponent, SelectComponent,
@@ -16239,7 +16701,8 @@ class TinSpaModule {
16239
16701
  AppModelsComponent, LoanProductsComponent, LoansComponent, LoanPaymentsComponent, WelcomeComponent, // Tin-SPA modules welcome
16240
16702
  TermsDialogComponent, PrivacyDialogComponent, // Changed: Added terms and privacy dialogs
16241
16703
  ChartsComponent, // Changed: Added ChartsComponent for config-driven chart rendering
16242
- FeatureDirective // Added: *spaFeature structural directive for plan-based feature gating
16704
+ FeatureDirective, // Added: *spaFeature structural directive for plan-based feature gating
16705
+ SpaLandingComponent // Added: Config-driven landing page component
16243
16706
  ], imports: [SpaMatModule,
16244
16707
  HttpClientModule,
16245
16708
  CurrencyInputModule,
@@ -16291,7 +16754,8 @@ class TinSpaModule {
16291
16754
  TermsDialogComponent, PrivacyDialogComponent, // Changed: Added terms and privacy dialogs to exports
16292
16755
  SelectLiteComponent, // Changed: Exported so pages outside TinSpaModule can use spa-select-lite
16293
16756
  ChartsComponent, // Changed: Exported ChartsComponent for consumer apps
16294
- FeatureDirective // Added: Exported *spaFeature directive for consumer apps
16757
+ FeatureDirective, // Added: Exported *spaFeature directive for consumer apps
16758
+ SpaLandingComponent // Added: Exported config-driven landing page component
16295
16759
  ] }); }
16296
16760
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TinSpaModule, providers: [
16297
16761
  { provide: HTTP_INTERCEPTORS, useClass: LoaderInterceptor, multi: true },
@@ -16331,7 +16795,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
16331
16795
  AppModelsComponent, LoanProductsComponent, LoansComponent, LoanPaymentsComponent, WelcomeComponent, // Tin-SPA modules welcome
16332
16796
  TermsDialogComponent, PrivacyDialogComponent, // Changed: Added terms and privacy dialogs
16333
16797
  ChartsComponent, // Changed: Added ChartsComponent for config-driven chart rendering
16334
- FeatureDirective // Added: *spaFeature structural directive for plan-based feature gating
16798
+ FeatureDirective, // Added: *spaFeature structural directive for plan-based feature gating
16799
+ SpaLandingComponent // Added: Config-driven landing page component
16335
16800
  ],
16336
16801
  imports: [
16337
16802
  SpaMatModule,
@@ -16387,7 +16852,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
16387
16852
  TermsDialogComponent, PrivacyDialogComponent, // Changed: Added terms and privacy dialogs to exports
16388
16853
  SelectLiteComponent, // Changed: Exported so pages outside TinSpaModule can use spa-select-lite
16389
16854
  ChartsComponent, // Changed: Exported ChartsComponent for consumer apps
16390
- FeatureDirective // Added: Exported *spaFeature directive for consumer apps
16855
+ FeatureDirective, // Added: Exported *spaFeature directive for consumer apps
16856
+ SpaLandingComponent // Added: Exported config-driven landing page component
16391
16857
  ],
16392
16858
  providers: [
16393
16859
  { provide: HTTP_INTERCEPTORS, useClass: LoaderInterceptor, multi: true },
@@ -16435,43 +16901,17 @@ class LoginComponent {
16435
16901
  else {
16436
16902
  this.redirectPath = "home";
16437
16903
  }
16438
- // Changed: Auto-redirect only if session is genuinely valid (non-expired token)
16439
- if (this.authService.hasValidSession()) {
16440
- this.router.navigate([this.redirectPath]);
16441
- return;
16442
- }
16443
- // Changed: If expired but refresh token exists, try silent refresh before showing login form
16444
- if (this.authService.hasRefreshToken()) {
16445
- this.isProcessing = true;
16446
- this.authService.silentRefresh().then((success) => {
16447
- this.isProcessing = false;
16448
- if (success) {
16449
- this.router.navigate([this.redirectPath]);
16450
- }
16451
- // If refresh fails, stay on login page — user must re-enter credentials
16452
- });
16453
- return;
16454
- }
16455
- if (this.route.snapshot.queryParams["sso"] == "msal" && this.appConfig.microsoftAuth) {
16456
- this.loginWithMS();
16457
- }
16458
- this.authService.autoLoginObserv.subscribe(x => {
16459
- this.autoLogin = x;
16460
- // console.log("AUTO LOGIN :" + this.autoLogin)
16461
- });
16462
- this.socialUser = null;
16463
- this.authService.socialUserObserv.subscribe((socialUser) => {
16464
- if (!socialUser)
16904
+ // Changed: Use shared tryRestoreSession — handles valid-session check and silent refresh in one call
16905
+ this.isProcessing = true;
16906
+ this.authService.tryRestoreSession().then(success => {
16907
+ this.isProcessing = false;
16908
+ if (success) {
16909
+ this.notifications();
16910
+ this.router.navigate([this.redirectPath]);
16465
16911
  return;
16466
- this.socialUser = socialUser;
16467
- this.user.userName = socialUser.id;
16468
- this.user.password = socialUser.id; //dummy data
16469
- this.user.token = socialUser.idToken;
16470
- this.user.authType = socialUser.provider;
16471
- this.login();
16912
+ }
16913
+ this.setupForm(); // Changed: Only set up login form if no session could be restored
16472
16914
  });
16473
- // this.authService.logoff();
16474
- this.style = this.dataService.appConfig.loginStyle ?? 'default';
16475
16915
  }
16476
16916
  loginWithMS() {
16477
16917
  this.msalService.loginPopup({
@@ -16501,6 +16941,25 @@ class LoginComponent {
16501
16941
  recoverAccount() {
16502
16942
  this.router.navigate(["recover-account"]);
16503
16943
  }
16944
+ // Changed: Extracted from ngOnInit — runs only when no session could be restored
16945
+ setupForm() {
16946
+ if (this.route.snapshot.queryParams["sso"] == "msal" && this.appConfig.microsoftAuth) {
16947
+ this.loginWithMS();
16948
+ }
16949
+ this.authService.autoLoginObserv.subscribe(x => { this.autoLogin = x; });
16950
+ this.socialUser = null;
16951
+ this.authService.socialUserObserv.subscribe((socialUser) => {
16952
+ if (!socialUser)
16953
+ return;
16954
+ this.socialUser = socialUser;
16955
+ this.user.userName = socialUser.id;
16956
+ this.user.password = socialUser.id;
16957
+ this.user.token = socialUser.idToken;
16958
+ this.user.authType = socialUser.provider;
16959
+ this.login();
16960
+ });
16961
+ this.style = this.dataService.appConfig.loginStyle ?? 'default';
16962
+ }
16504
16963
  login() {
16505
16964
  if (this.user.userName == "" || this.user.password == "") {
16506
16965
  this.messageService.toast("Please enter your credentials");
@@ -16554,11 +17013,14 @@ class LoginComponent {
16554
17013
  notifications() {
16555
17014
  if (this.appConfig.multitenant) {
16556
17015
  this.notificationsService.loadNotifications();
16557
- // Changed: Start SignalR connection for real-time notification count updates
16558
- const hubUrl = this.httpService.apiUrl.replace(/\/api\/$/, '/hubs/notification');
17016
+ // Changed: Start SignalR connections for real-time notification count and entity change updates
17017
+ const notificationHubUrl = this.httpService.apiUrl.replace(/\/api\/$/, '/hubs/notification');
17018
+ const dataHubUrl = this.httpService.apiUrl.replace(/\/api\/$/, '/hubs/data'); // Changed: Data hub for real-time table updates
16559
17019
  this.storageService.get(Constants.AUTH_TOKEN).then((token) => {
16560
- if (token)
16561
- this.signalRService.startConnection(hubUrl, token);
17020
+ if (token) {
17021
+ this.signalRService.startConnection(notificationHubUrl, token);
17022
+ this.signalRService.startDataConnection(dataHubUrl, token); // Changed: Connect to data hub
17023
+ }
16562
17024
  });
16563
17025
  }
16564
17026
  }
@@ -18157,7 +18619,9 @@ class PurchasingService {
18157
18619
  { name: 'delete', dialog: true, action: { url: 'purchaseorders?action=delete', method: 'post' } }
18158
18620
  ],
18159
18621
  loadAction: { url: 'purchaseorders/all/x' },
18160
- formConfig: this.purchaseOrderFormConfig
18622
+ formConfig: this.purchaseOrderFormConfig,
18623
+ // realTime: true, // Disabled: testing realtime on transactions table only
18624
+ entityName: 'PurchaseOrder'
18161
18625
  };
18162
18626
  //--------------------------Inventory Receipts-------------------------
18163
18627
  this.inventoryReceiptItemFormConfig = {
@@ -18311,7 +18775,9 @@ class PurchasingService {
18311
18775
  { name: 'delete', dialog: true, icon: { name: 'delete_forever', color: 'red' }, action: { url: 'inventoryreceipts?action=delete', method: 'post' }, confirm: { message: 'CAUTION: Deleting this receipt will reverse all inventory additions, any sales of this inventory, and all related accounting transactions. This action cannot be undone. Are you sure you want to proceed?' } },
18312
18776
  ],
18313
18777
  loadAction: { url: 'inventoryreceipts/all/x' },
18314
- formConfig: this.inventoryReceiptFormConfig
18778
+ formConfig: this.inventoryReceiptFormConfig,
18779
+ // realTime: true, // Disabled: testing realtime on transactions table only
18780
+ entityName: 'InventoryReceipt'
18315
18781
  };
18316
18782
  //--------------------------Supplier Payments-------------------------
18317
18783
  // Changed: Supplier payment form for recording payments against credit purchase receipts
@@ -18511,7 +18977,8 @@ class TransactionsComponent {
18511
18977
  this.accountingService = inject(AccountingService);
18512
18978
  this.pageConfig = {
18513
18979
  title: 'Transactions',
18514
- tableConfig: this.accountingService.transactionsTableConfig
18980
+ tableConfig: this.accountingService.transactionsTableConfig,
18981
+ searchTableConfig: this.accountingService.transactionSearchTableConfig // Changed: Enable search mode with date range, description, account filters
18515
18982
  };
18516
18983
  }
18517
18984
  ngOnInit() {
@@ -18812,7 +19279,9 @@ class SalesService {
18812
19279
  this.salesOrderDeliverButton
18813
19280
  ],
18814
19281
  loadAction: { url: 'salesorders/all/x' },
18815
- formConfig: this.salesOrderFormConfig
19282
+ formConfig: this.salesOrderFormConfig,
19283
+ // realTime: true, // Disabled: testing realtime on transactions table only
19284
+ entityName: 'SalesOrder'
18816
19285
  };
18817
19286
  //--------------------------Sale Items-------------------------
18818
19287
  this.saleItemFormConfig = {
@@ -18994,7 +19463,9 @@ class SalesService {
18994
19463
  this.saleVoidButton
18995
19464
  ],
18996
19465
  loadAction: { url: 'sales/all/x' },
18997
- formConfig: this.saleFormConfig
19466
+ formConfig: this.saleFormConfig,
19467
+ // realTime: true, // Disabled: testing realtime on transactions table only
19468
+ entityName: 'Sale'
18998
19469
  };
18999
19470
  }
19000
19471
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SalesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
@@ -19289,7 +19760,9 @@ class ProductionService {
19289
19760
  { name: 'delete', dialog: true, action: { url: 'productionrecipes?action=delete', method: 'post' } }
19290
19761
  ],
19291
19762
  loadAction: { url: 'productionrecipes/all/x' },
19292
- formConfig: this.recipeFormConfig
19763
+ formConfig: this.recipeFormConfig,
19764
+ // realTime: true, // Disabled: testing realtime on transactions table only
19765
+ entityName: 'ProductionRecipe'
19293
19766
  };
19294
19767
  // ==================== PRODUCTION ORDERS ====================
19295
19768
  this.orderItemFormConfig = {
@@ -19409,7 +19882,9 @@ class ProductionService {
19409
19882
  { name: 'delete', dialog: true, action: { url: 'productionorders?action=delete', method: 'post' }, visible: (x) => x.statusName === 'Draft' }
19410
19883
  ],
19411
19884
  loadAction: { url: 'productionorders/all/x' },
19412
- formConfig: this.orderFormConfig
19885
+ formConfig: this.orderFormConfig,
19886
+ // realTime: true, // Disabled: testing realtime on transactions table only
19887
+ entityName: 'ProductionOrder'
19413
19888
  };
19414
19889
  }
19415
19890
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ProductionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
@@ -19816,6 +20291,8 @@ class PayrollService {
19816
20291
  ],
19817
20292
  loadAction: { url: 'salarystructures/all/x' },
19818
20293
  formConfig: this.salaryStructureFormConfig,
20294
+ // realTime: true, // Disabled: testing realtime on transactions table only
20295
+ entityName: 'SalaryStructure'
19819
20296
  };
19820
20297
  //--------------------------Statutory Deduction Bands (child of StatutoryDeductionType)-------------------------
19821
20298
  this.statutoryDeductionBandFormConfig = {
@@ -19893,6 +20370,8 @@ class PayrollService {
19893
20370
  ],
19894
20371
  loadAction: { url: 'statutorydeductiontypes/all/x' },
19895
20372
  formConfig: this.statutoryDeductionTypeFormConfig,
20373
+ // realTime: true, // Disabled: testing realtime on transactions table only
20374
+ entityName: 'StatutoryDeductionType'
19896
20375
  };
19897
20376
  //--------------------------Payslip Lines (child of Payslip)-------------------------
19898
20377
  this.payslipLineFormConfig = {
@@ -20058,6 +20537,8 @@ class PayrollService {
20058
20537
  ],
20059
20538
  loadAction: { url: 'commissionconfigs/all/x' },
20060
20539
  formConfig: this.commissionConfigFormConfig,
20540
+ // realTime: true, // Disabled: testing realtime on transactions table only
20541
+ entityName: 'CommissionConfig'
20061
20542
  };
20062
20543
  // Employee commission configs child table
20063
20544
  this.employeeCommissionConfigsTableConfig = {
@@ -20117,6 +20598,8 @@ class PayrollService {
20117
20598
  ],
20118
20599
  loadAction: { url: 'commissionentries/all/x' },
20119
20600
  formConfig: this.commissionEntryFormConfig,
20601
+ // realTime: true, // Disabled: testing realtime on transactions table only
20602
+ entityName: 'CommissionEntry'
20120
20603
  };
20121
20604
  // Employee commission entries child table
20122
20605
  this.employeeCommissionEntriesTableConfig = {
@@ -20171,6 +20654,8 @@ class PayrollService {
20171
20654
  ],
20172
20655
  loadAction: { url: 'salaryadvances/all/x' },
20173
20656
  formConfig: this.salaryAdvanceFormConfig,
20657
+ // realTime: true, // Disabled: testing realtime on transactions table only
20658
+ entityName: 'SalaryAdvance'
20174
20659
  };
20175
20660
  // Employee salary advances child table
20176
20661
  this.employeeSalaryAdvancesTableConfig = {
@@ -20226,6 +20711,8 @@ class PayrollService {
20226
20711
  ],
20227
20712
  loadAction: { url: 'overtimeentries/all/x' },
20228
20713
  formConfig: this.overtimeEntryFormConfig,
20714
+ // realTime: true, // Disabled: testing realtime on transactions table only
20715
+ entityName: 'OvertimeEntry'
20229
20716
  };
20230
20717
  // Employee overtime entries child table
20231
20718
  this.employeeOvertimeHistoryTableConfig = {
@@ -20295,6 +20782,8 @@ class PayrollService {
20295
20782
  ],
20296
20783
  loadAction: { url: 'payrollruns/all/x' },
20297
20784
  formConfig: this.payrollRunFormConfig,
20785
+ // realTime: true, // Disabled: testing realtime on transactions table only
20786
+ entityName: 'PayrollRun'
20298
20787
  };
20299
20788
  }
20300
20789
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: PayrollService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
@@ -20534,7 +21023,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
20534
21023
  }]
20535
21024
  }] });
20536
21025
 
20537
- // Accounting module dashboard with financial KPI tiles and charts
21026
+ // Accounting module dashboard pure financial/account performance (invoice-specific moved to invoicing)
20538
21027
  class AccountingDashboardComponent {
20539
21028
  constructor() {
20540
21029
  this.summaryTiles = {
@@ -20542,45 +21031,56 @@ class AccountingDashboardComponent {
20542
21031
  { name: 'revenue', alias: 'Revenue (MTD)', color: '#4caf50', icon: 'trending_up', style: 'icon', info: 'MTD revenue' },
20543
21032
  { name: 'expenses', alias: 'Expenses (MTD)', color: '#f44336', icon: 'trending_down', style: 'icon', info: 'MTD expenses' },
20544
21033
  { name: 'netProfit', alias: 'Net Profit (MTD)', color: '#2196f3', icon: 'account_balance', style: 'icon', info: 'MTD profit' },
20545
- { name: 'outstandingAR', alias: 'Outstanding AR', color: '#ff9800', icon: 'receipt_long', style: 'icon', info: 'Unpaid invoices' },
20546
- { name: 'outstandingAP', alias: 'Outstanding AP', color: '#9c27b0', icon: 'payments', style: 'icon', info: 'Unpaid suppliers' },
20547
- { name: 'overdueInvoices', alias: 'Overdue', color: '#f44336', icon: 'warning', style: 'icon', info: 'Past due date' },
21034
+ { name: 'cashBalance', alias: 'Cash & Bank', color: '#ff9800', icon: 'account_balance_wallet', style: 'icon', info: 'Cash + bank total' }, // Changed: Replaced outstandingAR
21035
+ { name: 'totalLiabilities', alias: 'Liabilities', color: '#9c27b0', icon: 'credit_card', style: 'icon', info: 'Total liabilities' }, // Changed: Replaced outstandingAP
21036
+ { name: 'transactionsMTD', alias: 'Transactions', color: '#607d8b', icon: 'swap_horiz', style: 'icon', info: 'Posted this period' }, // Changed: Replaced overdueInvoices
20548
21037
  ],
20549
- loadAction: { url: 'accounts/dashboard-summary/x' }, // Changed: Use module-specific CustomCriteria endpoint
21038
+ loadAction: { url: 'accounts/dashboard-summary/x' },
20550
21039
  loadInit: true
20551
21040
  };
20552
- // Changed: Chart-style tiles now use loadAction with dataField for dynamic data
21041
+ // Changed: Replaced collectionRate/invoiceStatusSplit with profitMargin/expenseBreakdown
20553
21042
  this.chartTiles = {
20554
21043
  tiles: [
20555
- { name: 'collectionRate', alias: 'Collection Rate', color: '#4caf50', chart: { type: 'doughnut', gaugeColor: '#4caf50', height: 130, dataField: 'collectionRate' }, footer: 'Invoice collection rate', footerIcon: 'check_circle' }, // Changed: dataField replaces gaugeValue
20556
- { name: 'revenueTrendSpark', alias: 'Revenue Trend', color: '#2196f3', chart: { type: 'line', color: '#2196f3', height: 100, dataField: 'revenueTrendSpark' }, footer: 'Last 12 months', footerIcon: 'show_chart' }, // Changed: dataField replaces inline data
20557
- { name: 'invoiceStatusSplit', alias: 'Invoice Status', color: '#ff9800', chart: { type: 'pie', colors: ['#9e9e9e', '#2196f3', '#4caf50', '#f44336', '#ff9800'], height: 130, dataField: 'invoiceStatusSplit' }, footer: 'Current distribution', footerIcon: 'receipt' }, // Changed: dataField replaces inline data
20558
- { name: 'expenseTrendSpark', alias: 'Expense Trend', color: '#f44336', chart: { type: 'bar', colors: ['#f44336', '#ef5350', '#e57373', '#ef9a9a', '#ffcdd2', '#f44336'], height: 100, dataField: 'expenseTrendSpark' }, footer: 'Last 6 months', footerIcon: 'trending_down' }, // Changed: dataField replaces inline data
21044
+ { name: 'profitMargin', alias: 'Profit Margin', color: '#4caf50', chart: { type: 'doughnut', gaugeColor: '#4caf50', height: 130, dataField: 'profitMargin' }, footer: 'Net profit / revenue', footerIcon: 'pie_chart' }, // Changed: Replaced collectionRate
21045
+ { name: 'revenueTrendSpark', alias: 'Revenue Trend', color: '#2196f3', chart: { type: 'line', color: '#2196f3', height: 100, dataField: 'revenueTrendSpark' }, footer: 'Last 12 months', footerIcon: 'show_chart' },
21046
+ { name: 'expenseBreakdown', alias: 'Expense Breakdown', color: '#ff9800', chart: { type: 'pie', colors: ['#f44336', '#ff5722', '#ff9800', '#ffc107', '#ffeb3b', '#9e9e9e'], height: 130, dataField: 'expenseBreakdown' }, footer: 'Top 5 + Other', footerIcon: 'donut_large' }, // Changed: Replaced invoiceStatusSplit
21047
+ { name: 'expenseTrendSpark', alias: 'Expense Trend', color: '#f44336', chart: { type: 'bar', colors: ['#f44336', '#ef5350', '#e57373', '#ef9a9a', '#ffcdd2', '#f44336'], height: 100, dataField: 'expenseTrendSpark' }, footer: 'Last 6 months', footerIcon: 'trending_down' },
20559
21048
  ],
20560
- loadAction: { url: 'accounts/dashboard-chart-tiles/x' }, // Changed: Load chart tile data from API
21049
+ loadAction: { url: 'accounts/dashboard-chart-tiles/x' },
20561
21050
  loadInit: true
20562
21051
  };
21052
+ // Changed: Replaced arAging/invoiceStatus with cashFlow/transactionVolume
20563
21053
  this.chartConfig = {
20564
21054
  charts: [
20565
- { name: 'revenueTrend', title: 'Revenue vs Expenses Trend', type: 'line', height: '300px', showPoints: true, tension: 0.4, colors: ['#4caf50', '#f44336'], showLegend: true }, // Changed: showLegend for line chart
20566
- { name: 'arAging', title: 'AR Aging Breakdown', type: 'bar', height: '300px', colors: ['#4caf50', '#ff9800', '#ff5722', '#f44336', '#b71c1c'] },
21055
+ { name: 'revenueTrend', title: 'Revenue vs Expenses Trend', type: 'line', height: '300px', showPoints: true, tension: 0.4, colors: ['#4caf50', '#f44336'], showLegend: true },
21056
+ { name: 'cashFlow', title: 'Monthly Cash Flow', type: 'bar', height: '300px', colors: ['#4caf50', '#f44336'], showLegend: true }, // Changed: Replaced arAging
20567
21057
  { name: 'accountBalances', title: 'Account Balances by Type', type: 'bar', height: '300px', colors: ['#2196f3', '#f44336', '#4caf50', '#ff9800', '#9c27b0'] },
20568
- { name: 'invoiceStatus', title: 'Invoice Status Distribution', type: 'doughnut', height: '300px', colors: ['#9e9e9e', '#2196f3', '#4caf50', '#f44336', '#ff9800'] },
21058
+ { name: 'transactionVolume', title: 'Monthly Transaction Volume', type: 'bar', height: '300px', colors: ['#7c4dff'] }, // Changed: Replaced invoiceStatus
20569
21059
  ],
20570
- loadAction: { url: 'accounts/dashboard-charts/x' }, // Changed: Use module-specific CustomCriteria endpoint
21060
+ loadAction: { url: 'accounts/dashboard-charts/x' },
20571
21061
  loadInit: true,
20572
21062
  columns: 2
20573
21063
  };
21064
+ // Changed: New cash/bank running balance line chart with separate data source
21065
+ this.cashBankChartConfig = {
21066
+ charts: [
21067
+ { name: 'cashBankBalances', title: 'Cash & Bank Balances (30 Days)', type: 'line', height: '500px', showPoints: true, tension: 0.4, showLegend: true }, // Changed: Increased height for more granular values
21068
+ ],
21069
+ loadAction: { url: 'accounts/dashboard-cash-balances/x' },
21070
+ loadInit: true,
21071
+ columns: 1
21072
+ };
20574
21073
  }
20575
21074
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AccountingDashboardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
20576
21075
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: AccountingDashboardComponent, isStandalone: false, selector: "spa-accounting-dashboard", ngImport: i0, template: `
20577
21076
  <div class="dashboard-container">
20578
21077
  <h4 class="dashboard-title"><mat-icon>account_balance</mat-icon> Accounting Dashboard</h4>
20579
21078
  <spa-tiles [config]="summaryTiles"></spa-tiles>
20580
- <!-- Changed: Chart tiles for mini financial indicators -->
20581
21079
  <div style="margin-top: 16px;"></div>
20582
21080
  <spa-tiles [config]="chartTiles"></spa-tiles>
20583
21081
  <spa-charts [config]="chartConfig"></spa-charts>
21082
+ <!-- Changed: Separate cash/bank balance chart with own data source -->
21083
+ <spa-charts [config]="cashBankChartConfig"></spa-charts>
20584
21084
  </div>
20585
21085
  `, isInline: true, styles: [".dashboard-container{padding:16px}.dashboard-title{display:flex;align-items:center;gap:8px;margin-bottom:16px;color:#333;font-weight:500}\n"], dependencies: [{ kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: TilesComponent, selector: "spa-tiles", inputs: ["config", "lastSearch", "data", "reload"], outputs: ["tileActionSelected", "tileClick", "tileUnClick"] }, { kind: "component", type: ChartsComponent, selector: "spa-charts", inputs: ["config", "data", "reload"] }] }); }
20586
21086
  }
@@ -20590,10 +21090,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
20590
21090
  <div class="dashboard-container">
20591
21091
  <h4 class="dashboard-title"><mat-icon>account_balance</mat-icon> Accounting Dashboard</h4>
20592
21092
  <spa-tiles [config]="summaryTiles"></spa-tiles>
20593
- <!-- Changed: Chart tiles for mini financial indicators -->
20594
21093
  <div style="margin-top: 16px;"></div>
20595
21094
  <spa-tiles [config]="chartTiles"></spa-tiles>
20596
21095
  <spa-charts [config]="chartConfig"></spa-charts>
21096
+ <!-- Changed: Separate cash/bank balance chart with own data source -->
21097
+ <spa-charts [config]="cashBankChartConfig"></spa-charts>
20597
21098
  </div>
20598
21099
  `, standalone: false, styles: [".dashboard-container{padding:16px}.dashboard-title{display:flex;align-items:center;gap:8px;margin-bottom:16px;color:#333;font-weight:500}\n"] }]
20599
21100
  }] });
@@ -20731,8 +21232,8 @@ class OverviewDashboardComponent {
20731
21232
  { name: 'expenses', alias: 'Expenses (MTD)', color: '#f44336', icon: 'trending_down', style: 'icon', info: 'MTD expenses' },
20732
21233
  { name: 'netProfit', alias: 'Net Profit (MTD)', color: '#2196f3', icon: 'account_balance', style: 'icon', info: 'MTD profit' },
20733
21234
  { name: 'stockValue', alias: 'Stock Value', color: '#ff9800', icon: 'inventory_2', style: 'icon', info: 'Inventory value' },
20734
- { name: 'outstandingAR', alias: 'Outstanding AR', color: '#9c27b0', icon: 'receipt_long', style: 'icon', info: 'Unpaid invoices' },
20735
- { name: 'outstandingAP', alias: 'Outstanding AP', color: '#e91e63', icon: 'payments', style: 'icon', info: 'Unpaid suppliers' },
21235
+ { name: 'cashBalance', alias: 'Cash & Bank', color: '#9c27b0', icon: 'account_balance_wallet', style: 'icon', info: 'Cash + bank total' }, // Changed: Replaced outstandingAR
21236
+ { name: 'totalLiabilities', alias: 'Liabilities', color: '#e91e63', icon: 'credit_card', style: 'icon', info: 'Total liabilities' }, // Changed: Replaced outstandingAP
20736
21237
  ],
20737
21238
  loadAction: { url: 'dashboard/overview/summary' },
20738
21239
  loadInit: true
@@ -20824,6 +21325,7 @@ class InvoiceDashboardComponent {
20824
21325
  { name: 'agingBreakdown', title: 'AR Aging Breakdown', type: 'bar', height: '300px', colors: ['#4caf50', '#ff9800', '#ff5722', '#f44336', '#b71c1c'] },
20825
21326
  { name: 'topCustomers', title: 'Top Customers by Outstanding', type: 'bar', height: '300px', colors: ['#1565c0', '#1976d2', '#1e88e5', '#2196f3', '#42a5f5'] },
20826
21327
  { name: 'paymentTrend', title: 'Payment Collection Trend', type: 'line', height: '300px', showPoints: true, tension: 0.4, colors: ['#4caf50'], showLegend: true },
21328
+ { name: 'collectionPerformance', title: 'Collection Performance (12 Months)', type: 'line', height: '300px', showPoints: true, tension: 0.4, colors: ['#f57c00', '#4caf50'], showLegend: true }, // Changed: Avg days to pay + on-time rate trend
20827
21329
  ],
20828
21330
  loadAction: { url: 'invoices/dashboard-charts/x' },
20829
21331
  loadInit: true,
@@ -22167,6 +22669,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
22167
22669
  }]
22168
22670
  }] });
22169
22671
 
22672
+ // Config interfaces for the spa-landing component
22673
+ // Consumer apps provide this config to drive the entire landing page
22674
+ // Alsquare brand SVG — dark variant (for navbar on light backgrounds)
22675
+ const ALSQUARE_SVG_DARK = `<svg viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
22676
+ <path d="M32 62C17.6 62 6 50.4 6 36S17.6 10 32 10c9.2 0 17.3 4.9 21.8 12.2" stroke="#1a3a7a" stroke-width="9" stroke-linecap="round" fill="none"/>
22677
+ <circle cx="32" cy="36" r="10" fill="white" stroke="#1a3a7a" stroke-width="7"/>
22678
+ <circle cx="32" cy="36" r="5.5" fill="#e8a020"/>
22679
+ <line x1="48" y1="22" x2="48" y2="62" stroke="#1a3a7a" stroke-width="9" stroke-linecap="round"/>
22680
+ <rect x="56" y="6" width="7" height="56" rx="3.5" fill="#e8a020"/>
22681
+ <rect x="67" y="0" width="6" height="6" rx="1" fill="#1a3a7a"/>
22682
+ <rect x="67" y="9" width="6" height="6" rx="1" fill="#1a3a7a"/>
22683
+ <rect x="75" y="0" width="5" height="5" rx="1" fill="#1a3a7a" opacity="0.7"/>
22684
+ <rect x="75" y="8" width="5" height="5" rx="1" fill="#1a3a7a" opacity="0.5"/>
22685
+ </svg>`;
22686
+ // Alsquare brand SVG — white variant (for footer on dark backgrounds)
22687
+ const ALSQUARE_SVG_WHITE = `<svg viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
22688
+ <path d="M32 62C17.6 62 6 50.4 6 36S17.6 10 32 10c9.2 0 17.3 4.9 21.8 12.2" stroke="#ffffff" stroke-width="9" stroke-linecap="round" fill="none"/>
22689
+ <circle cx="32" cy="36" r="10" fill="#0f172a" stroke="#ffffff" stroke-width="7"/>
22690
+ <circle cx="32" cy="36" r="5.5" fill="#e8a020"/>
22691
+ <line x1="48" y1="22" x2="48" y2="62" stroke="#ffffff" stroke-width="9" stroke-linecap="round"/>
22692
+ <rect x="56" y="6" width="7" height="56" rx="3.5" fill="#e8a020"/>
22693
+ <rect x="67" y="0" width="6" height="6" rx="1" fill="#ffffff"/>
22694
+ <rect x="67" y="9" width="6" height="6" rx="1" fill="#ffffff"/>
22695
+ <rect x="75" y="0" width="5" height="5" rx="1" fill="#ffffff" opacity="0.7"/>
22696
+ <rect x="75" y="8" width="5" height="5" rx="1" fill="#ffffff" opacity="0.5"/>
22697
+ </svg>`;
22698
+
22170
22699
  /*
22171
22700
  * Public API Surface of tin-spa
22172
22701
  */
@@ -22176,5 +22705,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
22176
22705
  * Generated bundle index. Do not edit.
22177
22706
  */
22178
22707
 
22179
- export { Account, AccountsComponent as AccountingAccountsComponent, AggregatesComponent as AccountingAggregatesComponent, AgingComponent as AccountingAgingComponent, CurrenciesComponent as AccountingCurrenciesComponent, AccountingDashboardComponent, InvoicesComponent as AccountingInvoicesComponent, ReportsComponent as AccountingReportsComponent, AccountingService, StatementComponent as AccountingStatementComponent, TransactionTypesComponent as AccountingTransactionTypesComponent, TransactionsComponent as AccountingTransactionsComponent, Action, ActivityComponent, AdminModule, AlertComponent, AlertConfig, AlertMessage, ApiResponse, AppConfig, AppModelsComponent, AssetStatus, AttachComponent, AuthService, BillingPageComponent, BrandsComponent, CacheConfig, CapItem, CapsulesComponent, CategoriesComponent, ChangePasswordComponent, ChangeUserPassword, ChartConfig, ChartsComponent, CheckComponent, ChipsComponent, Constants, Core, CreateAccountComponent, CustomersComponent, DataServiceLib, DateComponent, DatetimeComponent, DepartmentsComponent, DetailsDialog, DetailsDialogConfig, DetailsDialogProcessor, DetailsSource, DialogService, EmailComponent, EmployeesComponent, ExportService, FeatureDirective, FilterComponent, FormComponent, FormConfig, GeneralService, GradesComponent, GroupsComponent, HtmlComponent, HttpService, IndexModule, InventoryDashboardComponent, InventoryReceiptStatus, InventoryService, InvoiceDashboardComponent, InvoiceItemType, InvoiceStatus, LabelComponent, ListDialogComponent, ListDialogConfig, LoaderComponent, LoaderService, LoanPaymentsComponent, LoanProductsComponent, LoansComponent, LoansService, LogLevel, LogService, LoginComponent, LogsComponent, MembershipComponent, MessageService, MoneyComponent, MovementType, NavMenuComponent, NotesComponent, NotesConfig, NotificationsService, NumberComponent, OnboardingComponent, OptionComponent, OverviewDashboardComponent, PageComponent, PageConfig, PayrollDashboardComponent, PlansComponent, PositionsComponent, PreferencesComponent, PrivacyDialogComponent, Profile, ProfileComponent, PurchasingDashboardComponent, PushNotificationService, RecoverAccountComponent, Register, Role, RoleAccess, RolesComponent, SalesDashboardComponent, SearchComponent, SearchConfig, SecurityConfig, SelectBitwiseComponent, SelectComponent, SelectLiteComponent, SelectMultiComponent, SettingsComponent, SignupComponent, SignupData, SpaAdminModule, SpaIndexModule, SpaMatModule, SpaUserModule, StatusesComponent, Step, StepConfig, StepsComponent, StorageService, SubCategoriesComponent, SubscriptionPageComponent, SubscriptionService, SuppliersComponent, TabService, TableComponent, TableConfig, TabsComponent, TabsInternalComponent, TabsLiteComponent, TasksComponent, TenantsComponent, TermsDialogComponent, TextAreaComponent, TextComponent, TextMaskComponent, TextMultiComponent, TextSingleComponent, TileConfig, TilesComponent, TinSpaComponent, TinSpaModule, TinSpaService, TitleActionsComponent, TransactionTiming, UnitOfMeasure, UpdateService, User, UserModule, UsersComponent, ViewerComponent, WelcomeComponent, authGuard, dialogOptions, featureGuard, loginConfig, messageDialog, viewerDialog };
22708
+ export { ALSQUARE_SVG_DARK, ALSQUARE_SVG_WHITE, Account, AccountsComponent as AccountingAccountsComponent, AggregatesComponent as AccountingAggregatesComponent, AgingComponent as AccountingAgingComponent, CurrenciesComponent as AccountingCurrenciesComponent, AccountingDashboardComponent, InvoicesComponent as AccountingInvoicesComponent, ReportsComponent as AccountingReportsComponent, AccountingService, StatementComponent as AccountingStatementComponent, TransactionTypesComponent as AccountingTransactionTypesComponent, TransactionsComponent as AccountingTransactionsComponent, Action, ActivityComponent, AdminModule, AlertComponent, AlertConfig, AlertMessage, ApiResponse, AppConfig, AppModelsComponent, AssetStatus, AttachComponent, AuthService, BillingPageComponent, BrandsComponent, CacheConfig, CapItem, CapsulesComponent, CategoriesComponent, ChangePasswordComponent, ChangeUserPassword, ChartConfig, ChartsComponent, CheckComponent, ChipsComponent, Constants, Core, CreateAccountComponent, CustomersComponent, DataServiceLib, DateComponent, DatetimeComponent, DepartmentsComponent, DetailsDialog, DetailsDialogConfig, DetailsDialogProcessor, DetailsSource, DialogService, EmailComponent, EmployeesComponent, ExportService, FeatureDirective, FilterComponent, FormComponent, FormConfig, GeneralService, GradesComponent, GroupsComponent, HtmlComponent, HttpService, IndexModule, InventoryDashboardComponent, InventoryReceiptStatus, InventoryService, InvoiceDashboardComponent, InvoiceItemType, InvoiceStatus, LabelComponent, ListDialogComponent, ListDialogConfig, LoaderComponent, LoaderService, LoanPaymentsComponent, LoanProductsComponent, LoansComponent, LoansService, LogLevel, LogService, LoginComponent, LogsComponent, MembershipComponent, MessageService, MoneyComponent, MovementType, NavMenuComponent, NotesComponent, NotesConfig, NotificationsService, NumberComponent, OnboardingComponent, OptionComponent, OverviewDashboardComponent, PageComponent, PageConfig, PayrollDashboardComponent, PlansComponent, PositionsComponent, PreferencesComponent, PrivacyDialogComponent, Profile, ProfileComponent, PurchasingDashboardComponent, PushNotificationService, RecoverAccountComponent, Register, Role, RoleAccess, RolesComponent, SalesDashboardComponent, SearchComponent, SearchConfig, SecurityConfig, SelectBitwiseComponent, SelectComponent, SelectLiteComponent, SelectMultiComponent, SettingsComponent, SignupComponent, SignupData, SpaAdminModule, SpaIndexModule, SpaLandingComponent, SpaMatModule, SpaUserModule, StatusesComponent, Step, StepConfig, StepsComponent, StorageService, SubCategoriesComponent, SubscriptionPageComponent, SubscriptionService, SuppliersComponent, TabService, TableComponent, TableConfig, TabsComponent, TabsInternalComponent, TabsLiteComponent, TasksComponent, TenantsComponent, TermsDialogComponent, TextAreaComponent, TextComponent, TextMaskComponent, TextMultiComponent, TextSingleComponent, TileConfig, TilesComponent, TinSpaComponent, TinSpaModule, TinSpaService, TitleActionsComponent, TransactionTiming, UnitOfMeasure, UpdateService, User, UserModule, UsersComponent, ViewerComponent, WelcomeComponent, authGuard, dialogOptions, featureGuard, loginConfig, messageDialog, viewerDialog };
22180
22709
  //# sourceMappingURL=tin-spa.mjs.map